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>
25 #include "props_io.hxx"
33 SG_USING_STD(istream);
34 SG_USING_STD(ifstream);
35 SG_USING_STD(ostream);
36 SG_USING_STD(ofstream);
41 #define DEFAULT_MODE (SGPropertyNode::READ|SGPropertyNode::WRITE)
45 ////////////////////////////////////////////////////////////////////////
46 // Property list visitor, for XML parsing.
47 ////////////////////////////////////////////////////////////////////////
49 class PropsVisitor : public XMLVisitor
53 PropsVisitor (SGPropertyNode * root, const string &base, int default_mode = 0)
54 : _default_mode(default_mode), _root(root), _level(0), _base(base), _hasException(false) {}
56 virtual ~PropsVisitor () {}
60 void startElement (const char * name, const XMLAttributes &atts);
61 void endElement (const char * name);
62 void data (const char * s, int length);
63 void warning (const char * message, int line, int column);
65 bool hasException () const { return _hasException; }
66 sg_io_exception &getException () { return _exception; }
67 void setException (const sg_io_exception &exception) {
68 _exception = exception;
76 State () : node(0), type(""), mode(DEFAULT_MODE) {}
77 State (SGPropertyNode * _node, const char * _type, int _mode)
78 : node(_node), type(_type), mode(_mode) {}
79 SGPropertyNode * node;
82 map<string,int> counters;
85 State &state () { return _state_stack[_state_stack.size() - 1]; }
87 void push_state (SGPropertyNode * node, const char * type, int mode) {
89 _state_stack.push_back(State(node, "unspecified", mode));
91 _state_stack.push_back(State(node, type, mode));
97 _state_stack.pop_back();
103 SGPropertyNode * _root;
105 vector<State> _state_stack;
107 sg_io_exception _exception;
112 PropsVisitor::startXML ()
115 _state_stack.resize(0);
119 PropsVisitor::endXML ()
122 _state_stack.resize(0);
127 * Check a yes/no flag, with default.
130 checkFlag (const char * flag, bool defaultState = true)
134 else if (!strcmp(flag, "y"))
136 else if (!strcmp(flag, "n"))
139 string message = "Unrecognized flag value '";
142 // FIXME: add location info
143 throw sg_io_exception(message, "SimGear Property Reader");
148 PropsVisitor::startElement (const char * name, const XMLAttributes &atts)
153 if (strcmp(name, "PropertyList")) {
154 string message = "Root element name is ";
156 message += "; expected PropertyList";
157 throw sg_io_exception(message, "SimGear Property Reader");
160 // Check for an include.
161 attval = atts.getValue("include");
163 SGPath path(SGPath(_base).dir());
166 readProperties(path.str(), _root);
167 } catch (sg_io_exception &e) {
172 push_state(_root, "", DEFAULT_MODE);
178 attval = atts.getValue("n");
181 index = atoi(attval);
182 st.counters[name] = SG_MAX2(st.counters[name], index+1);
184 index = st.counters[name];
188 // Got the index, so grab the node.
189 SGPropertyNode * node = st.node->getChild(name, index, true);
191 // Get the access-mode attributes,
192 // but don't set yet (in case they
193 // prevent us from recording the value).
194 int mode = _default_mode;
196 attval = atts.getValue("read");
197 if (checkFlag(attval, true))
198 mode |= SGPropertyNode::READ;
199 attval = atts.getValue("write");
200 if (checkFlag(attval, true))
201 mode |= SGPropertyNode::WRITE;
202 attval = atts.getValue("archive");
203 if (checkFlag(attval, false))
204 mode |= SGPropertyNode::ARCHIVE;
205 attval = atts.getValue("trace-read");
206 if (checkFlag(attval, false))
207 mode |= SGPropertyNode::TRACE_READ;
208 attval = atts.getValue("trace-write");
209 if (checkFlag(attval, false))
210 mode |= SGPropertyNode::TRACE_WRITE;
211 attval = atts.getValue("userarchive");
212 if (checkFlag(attval, false))
213 mode |= SGPropertyNode::USERARCHIVE;
215 // Check for an alias.
216 attval = atts.getValue("alias");
218 if (!node->alias(attval))
219 SG_LOG(SG_INPUT, SG_ALERT, "Failed to set alias to " << attval);
222 // Check for an include.
223 attval = atts.getValue("include");
225 SGPath path(SGPath(_base).dir());
228 readProperties(path.str(), node);
229 } catch (sg_io_exception &e) {
234 const char *type = atts.getValue("type");
237 push_state(node, type, mode);
242 PropsVisitor::endElement (const char * name)
247 // If there are no children and it's
248 // not an alias, then it's a leaf value.
249 if (st.node->nChildren() == 0 && !st.node->isAlias()) {
250 if (st.type == "bool") {
251 if (_data == "true" || atoi(_data.c_str()) != 0)
252 ret = st.node->setBoolValue(true);
254 ret = st.node->setBoolValue(false);
255 } else if (st.type == "int") {
256 ret = st.node->setIntValue(atoi(_data.c_str()));
257 } else if (st.type == "long") {
258 ret = st.node->setLongValue(strtol(_data.c_str(), 0, 0));
259 } else if (st.type == "float") {
260 ret = st.node->setFloatValue(atof(_data.c_str()));
261 } else if (st.type == "double") {
262 ret = st.node->setDoubleValue(strtod(_data.c_str(), 0));
263 } else if (st.type == "string") {
264 ret = st.node->setStringValue(_data.c_str());
265 } else if (st.type == "unspecified") {
266 ret = st.node->setUnspecifiedValue(_data.c_str());
268 string message = "Unrecognized data type '";
271 // FIXME: add location information
272 throw sg_io_exception(message, "SimGear Property Reader");
275 SG_LOG(SG_INPUT, SG_ALERT, "readProperties: Failed to set "
276 << st.node->getPath() << " to value \""
277 << _data << "\" with type " << st.type);
280 // Set the access-mode attributes now,
281 // once the value has already been
283 st.node->setAttributes(st.mode);
289 PropsVisitor::data (const char * s, int length)
291 if (state().node->nChildren() == 0)
292 _data.append(string(s, length));
296 PropsVisitor::warning (const char * message, int line, int column)
298 SG_LOG(SG_INPUT, SG_ALERT, "readProperties: warning: "
299 << message << " at line " << line << ", column " << column);
304 ////////////////////////////////////////////////////////////////////////
305 // Property list reader.
306 ////////////////////////////////////////////////////////////////////////
310 * Read properties from an input stream.
312 * @param input The input stream containing an XML property file.
313 * @param start_node The root node for reading properties.
314 * @param base A base path for resolving external include references.
315 * @return true if the read succeeded, false otherwise.
318 readProperties (istream &input, SGPropertyNode * start_node,
319 const string &base, int default_mode)
321 PropsVisitor visitor(start_node, base, default_mode);
322 readXML(input, visitor, base);
323 if (visitor.hasException())
324 throw visitor.getException();
329 * Read properties from a file.
331 * @param file A string containing the file path.
332 * @param start_node The root node for reading properties.
333 * @return true if the read succeeded, false otherwise.
336 readProperties (const string &file, SGPropertyNode * start_node,
339 PropsVisitor visitor(start_node, file, default_mode);
340 readXML(file, visitor);
341 if (visitor.hasException())
342 throw visitor.getException();
347 * Read properties from an in-memory buffer.
349 * @param buf A character buffer containing the xml data.
350 * @param size The size/length of the buffer in bytes
351 * @param start_node The root node for reading properties.
352 * @return true if the read succeeded, false otherwise.
354 void readProperties (const char *buf, const int size,
355 SGPropertyNode * start_node, int default_mode)
357 PropsVisitor visitor(start_node, "", default_mode);
358 readXML(buf, size, visitor);
359 if (visitor.hasException())
360 throw visitor.getException();
364 ////////////////////////////////////////////////////////////////////////
365 // Property list writer.
366 ////////////////////////////////////////////////////////////////////////
368 #define INDENT_STEP 2
371 * Return the type name.
374 getTypeName (SGPropertyNode::Type type)
377 case SGPropertyNode::UNSPECIFIED:
378 return "unspecified";
379 case SGPropertyNode::BOOL:
381 case SGPropertyNode::INT:
383 case SGPropertyNode::LONG:
385 case SGPropertyNode::FLOAT:
387 case SGPropertyNode::DOUBLE:
389 case SGPropertyNode::STRING:
391 case SGPropertyNode::ALIAS:
392 case SGPropertyNode::NONE:
393 return "unspecified";
396 // keep the compiler from squawking
397 return "unspecified";
402 * Escape characters for output.
405 writeData (ostream &output, const string &data)
407 for (int i = 0; i < (int)data.size(); i++) {
426 doIndent (ostream &output, int indent)
428 while (indent-- > 0) {
435 writeAtts (ostream &output, const SGPropertyNode * node)
437 int index = node->getIndex();
440 output << " n=\"" << index << '"';
443 if (!node->getAttribute(SGPropertyNode::READ))
444 output << " read=\"n\"";
446 if (!node->getAttribute(SGPropertyNode::WRITE))
447 output << " write=\"n\"";
449 if (node->getAttribute(SGPropertyNode::ARCHIVE))
450 output << " archive=\"y\"";
457 * Test whether a node is archivable or has archivable descendants.
460 isArchivable (const SGPropertyNode * node, SGPropertyNode::Attribute archive_flag)
462 // FIXME: it's inefficient to do this all the time
463 if (node->getAttribute(archive_flag))
466 int nChildren = node->nChildren();
467 for (int i = 0; i < nChildren; i++)
468 if (isArchivable(node->getChild(i), archive_flag))
476 writeNode (ostream &output, const SGPropertyNode * node,
477 bool write_all, int indent, SGPropertyNode::Attribute archive_flag)
479 // Don't write the node or any of
480 // its descendants unless it is
481 // allowed to be archived.
482 if (!write_all && !isArchivable(node, archive_flag))
483 return true; // Everything's OK, but we won't write.
485 const string name = node->getName();
486 int nChildren = node->nChildren();
488 // If there is a literal value,
490 if (node->hasValue() && (write_all || node->getAttribute(archive_flag))) {
491 doIndent(output, indent);
492 output << '<' << name;
493 writeAtts(output, node);
494 if (node->isAlias() && node->getAliasTarget() != 0) {
495 output << " alias=\"" << node->getAliasTarget()->getPath()
498 if (node->getType() != SGPropertyNode::UNSPECIFIED)
499 output << " type=\"" << getTypeName(node->getType()) << '"';
501 writeData(output, node->getStringValue());
502 output << "</" << name << '>' << endl;
506 // If there are children, write them next.
508 doIndent(output, indent);
509 output << '<' << name;
510 writeAtts(output, node);
511 output << '>' << endl;
512 for (int i = 0; i < nChildren; i++)
513 writeNode(output, node->getChild(i), write_all, indent + INDENT_STEP, archive_flag);
514 doIndent(output, indent);
515 output << "</" << name << '>' << endl;
523 writeProperties (ostream &output, const SGPropertyNode * start_node,
524 bool write_all, SGPropertyNode::Attribute archive_flag)
526 int nChildren = start_node->nChildren();
528 output << "<?xml version=\"1.0\"?>" << endl << endl;
529 output << "<PropertyList>" << endl;
531 for (int i = 0; i < nChildren; i++) {
532 writeNode(output, start_node->getChild(i), write_all, INDENT_STEP, archive_flag);
535 output << "</PropertyList>" << endl;
540 writeProperties (const string &file, const SGPropertyNode * start_node,
541 bool write_all, SGPropertyNode::Attribute archive_flag)
543 SGPath path(file.c_str());
544 path.create_dir(0777);
546 ofstream output(file.c_str());
548 writeProperties(output, start_node, write_all, archive_flag);
550 throw sg_io_exception("Cannot open file", sg_location(file));
556 ////////////////////////////////////////////////////////////////////////
557 // Copy properties from one tree to another.
558 ////////////////////////////////////////////////////////////////////////
562 * Copy one property tree to another.
564 * @param in The source property tree.
565 * @param out The destination property tree.
566 * @return true if all properties were copied, false if some failed
567 * (for example, if the property's value is tied read-only).
570 copyProperties (const SGPropertyNode *in, SGPropertyNode *out)
574 // First, copy the actual value,
576 if (in->hasValue()) {
577 switch (in->getType()) {
578 case SGPropertyNode::BOOL:
579 if (!out->setBoolValue(in->getBoolValue()))
582 case SGPropertyNode::INT:
583 if (!out->setIntValue(in->getIntValue()))
586 case SGPropertyNode::LONG:
587 if (!out->setLongValue(in->getLongValue()))
590 case SGPropertyNode::FLOAT:
591 if (!out->setFloatValue(in->getFloatValue()))
594 case SGPropertyNode::DOUBLE:
595 if (!out->setDoubleValue(in->getDoubleValue()))
598 case SGPropertyNode::STRING:
599 if (!out->setStringValue(in->getStringValue()))
602 case SGPropertyNode::UNSPECIFIED:
603 if (!out->setUnspecifiedValue(in->getStringValue()))
609 string message = "Unknown internal SGPropertyNode type";
610 message += in->getType();
611 throw sg_error(message, "SimGear Property Reader");
615 // copy the attributes.
616 out->setAttributes( in->getAttributes() );
618 // Next, copy the children.
619 int nChildren = in->nChildren();
620 for (int i = 0; i < nChildren; i++) {
621 const SGPropertyNode * in_child = in->getChild(i);
622 SGPropertyNode * out_child = out->getChild(in_child->getName(),
623 in_child->getIndex(),
625 if (!copyProperties(in_child, out_child))
632 // end of props_io.cxx