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"
30 #include <cstring> // strcmp()
44 #define DEFAULT_MODE (SGPropertyNode::READ|SGPropertyNode::WRITE)
48 ////////////////////////////////////////////////////////////////////////
49 // Property list visitor, for XML parsing.
50 ////////////////////////////////////////////////////////////////////////
52 class PropsVisitor : public XMLVisitor
56 PropsVisitor (SGPropertyNode * root, const string &base, int default_mode = 0)
57 : _default_mode(default_mode), _root(root), _level(0), _base(base), _hasException(false) {}
59 virtual ~PropsVisitor () {}
63 void startElement (const char * name, const XMLAttributes &atts);
64 void endElement (const char * name);
65 void data (const char * s, int length);
66 void warning (const char * message, int line, int column);
68 bool hasException () const { return _hasException; }
69 sg_io_exception &getException () { return _exception; }
70 void setException (const sg_io_exception &exception) {
71 _exception = exception;
79 State () : node(0), type(""), mode(DEFAULT_MODE), omit(false) {}
80 State (SGPropertyNode * _node, const char * _type, int _mode, bool _omit)
81 : node(_node), type(_type), mode(_mode), omit(_omit) {}
82 SGPropertyNode * node;
86 map<string,int> counters;
89 State &state () { return _state_stack[_state_stack.size() - 1]; }
91 void push_state (SGPropertyNode * node, const char * type, int mode, bool omit = false) {
93 _state_stack.push_back(State(node, "unspecified", mode, omit));
95 _state_stack.push_back(State(node, type, mode, omit));
101 _state_stack.pop_back();
107 SGPropertyNode * _root;
110 vector<State> _state_stack;
112 sg_io_exception _exception;
117 PropsVisitor::startXML ()
120 _state_stack.resize(0);
124 PropsVisitor::endXML ()
127 _state_stack.resize(0);
132 * Check a yes/no flag, with default.
135 checkFlag (const char * flag, bool defaultState = true)
139 else if (!strcmp(flag, "y"))
141 else if (!strcmp(flag, "n"))
144 string message = "Unrecognized flag value '";
147 // FIXME: add location info
148 throw sg_io_exception(message, "SimGear Property Reader");
153 PropsVisitor::startElement (const char * name, const XMLAttributes &atts)
158 if (strcmp(name, "PropertyList")) {
159 string message = "Root element name is ";
161 message += "; expected PropertyList";
162 throw sg_io_exception(message, "SimGear Property Reader");
165 // Check for an include.
166 attval = atts.getValue("include");
168 SGPath path(SGPath(_base).dir());
171 readProperties(path.str(), _root);
172 } catch (sg_io_exception &e) {
177 push_state(_root, "", DEFAULT_MODE);
183 attval = atts.getValue("n");
186 index = atoi(attval);
187 st.counters[name] = SG_MAX2(st.counters[name], index+1);
189 index = st.counters[name];
193 // Got the index, so grab the node.
194 SGPropertyNode * node = st.node->getChild(name, index, true);
195 if (!node->getAttribute(SGPropertyNode::WRITE)) {
196 SG_LOG(SG_INPUT, SG_ALERT, "Not overwriting write-protected property "
197 << node->getPath(true));
201 // Get the access-mode attributes,
202 // but don't set yet (in case they
203 // prevent us from recording the value).
204 int mode = _default_mode;
206 attval = atts.getValue("read");
207 if (checkFlag(attval, true))
208 mode |= SGPropertyNode::READ;
209 attval = atts.getValue("write");
210 if (checkFlag(attval, true))
211 mode |= SGPropertyNode::WRITE;
212 attval = atts.getValue("archive");
213 if (checkFlag(attval, false))
214 mode |= SGPropertyNode::ARCHIVE;
215 attval = atts.getValue("trace-read");
216 if (checkFlag(attval, false))
217 mode |= SGPropertyNode::TRACE_READ;
218 attval = atts.getValue("trace-write");
219 if (checkFlag(attval, false))
220 mode |= SGPropertyNode::TRACE_WRITE;
221 attval = atts.getValue("userarchive");
222 if (checkFlag(attval, false))
223 mode |= SGPropertyNode::USERARCHIVE;
225 // Check for an alias.
226 attval = atts.getValue("alias");
228 if (!node->alias(attval))
229 SG_LOG(SG_INPUT, SG_ALERT, "Failed to set alias to " << attval);
232 // Check for an include.
234 attval = atts.getValue("include");
236 SGPath path(SGPath(_base).dir());
239 readProperties(path.str(), node);
240 } catch (sg_io_exception &e) {
244 attval = atts.getValue("omit-node");
245 omit = checkFlag(attval, false);
248 const char *type = atts.getValue("type");
251 push_state(node, type, mode, omit);
256 PropsVisitor::endElement (const char * name)
261 // If there are no children and it's
262 // not an alias, then it's a leaf value.
263 if (st.node->nChildren() == 0 && !st.node->isAlias()) {
264 if (st.type == "bool") {
265 if (_data == "true" || atoi(_data.c_str()) != 0)
266 ret = st.node->setBoolValue(true);
268 ret = st.node->setBoolValue(false);
269 } else if (st.type == "int") {
270 ret = st.node->setIntValue(atoi(_data.c_str()));
271 } else if (st.type == "long") {
272 ret = st.node->setLongValue(strtol(_data.c_str(), 0, 0));
273 } else if (st.type == "float") {
274 ret = st.node->setFloatValue(atof(_data.c_str()));
275 } else if (st.type == "double") {
276 ret = st.node->setDoubleValue(strtod(_data.c_str(), 0));
277 } else if (st.type == "string") {
278 ret = st.node->setStringValue(_data.c_str());
279 } else if (st.type == "unspecified") {
280 ret = st.node->setUnspecifiedValue(_data.c_str());
281 } else if (_level == 1) {
282 ret = true; // empty <PropertyList>
284 string message = "Unrecognized data type '";
287 // FIXME: add location information
288 throw sg_io_exception(message, "SimGear Property Reader");
291 SG_LOG(SG_INPUT, SG_ALERT, "readProperties: Failed to set "
292 << st.node->getPath() << " to value \""
293 << _data << "\" with type " << st.type);
296 // Set the access-mode attributes now,
297 // once the value has already been
299 st.node->setAttributes(st.mode);
302 State &parent = _state_stack[_state_stack.size() - 2];
303 int nChildren = st.node->nChildren();
304 for (int i = 0; i < nChildren; i++) {
305 SGPropertyNode *src = st.node->getChild(i);
306 const char *name = src->getName();
307 int index = parent.counters[name];
308 parent.counters[name]++;
309 SGPropertyNode *dst = parent.node->getChild(name, index, true);
310 copyProperties(src, dst);
312 parent.node->removeChild(st.node->getName(), st.node->getIndex(), false);
318 PropsVisitor::data (const char * s, int length)
320 if (state().node->nChildren() == 0)
321 _data.append(string(s, length));
325 PropsVisitor::warning (const char * message, int line, int column)
327 SG_LOG(SG_INPUT, SG_ALERT, "readProperties: warning: "
328 << message << " at line " << line << ", column " << column);
333 ////////////////////////////////////////////////////////////////////////
334 // Property list reader.
335 ////////////////////////////////////////////////////////////////////////
339 * Read properties from an input stream.
341 * @param input The input stream containing an XML property file.
342 * @param start_node The root node for reading properties.
343 * @param base A base path for resolving external include references.
344 * @return true if the read succeeded, false otherwise.
347 readProperties (istream &input, SGPropertyNode * start_node,
348 const string &base, int default_mode)
350 PropsVisitor visitor(start_node, base, default_mode);
351 readXML(input, visitor, base);
352 if (visitor.hasException())
353 throw visitor.getException();
358 * Read properties from a file.
360 * @param file A string containing the file path.
361 * @param start_node The root node for reading properties.
362 * @return true if the read succeeded, false otherwise.
365 readProperties (const string &file, SGPropertyNode * start_node,
368 PropsVisitor visitor(start_node, file, default_mode);
369 readXML(file, visitor);
370 if (visitor.hasException())
371 throw visitor.getException();
376 * Read properties from an in-memory buffer.
378 * @param buf A character buffer containing the xml data.
379 * @param size The size/length of the buffer in bytes
380 * @param start_node The root node for reading properties.
381 * @return true if the read succeeded, false otherwise.
383 void readProperties (const char *buf, const int size,
384 SGPropertyNode * start_node, int default_mode)
386 PropsVisitor visitor(start_node, "", default_mode);
387 readXML(buf, size, visitor);
388 if (visitor.hasException())
389 throw visitor.getException();
393 ////////////////////////////////////////////////////////////////////////
394 // Property list writer.
395 ////////////////////////////////////////////////////////////////////////
397 #define INDENT_STEP 2
400 * Return the type name.
403 getTypeName (SGPropertyNode::Type type)
406 case SGPropertyNode::UNSPECIFIED:
407 return "unspecified";
408 case SGPropertyNode::BOOL:
410 case SGPropertyNode::INT:
412 case SGPropertyNode::LONG:
414 case SGPropertyNode::FLOAT:
416 case SGPropertyNode::DOUBLE:
418 case SGPropertyNode::STRING:
420 case SGPropertyNode::ALIAS:
421 case SGPropertyNode::NONE:
422 return "unspecified";
425 // keep the compiler from squawking
426 return "unspecified";
431 * Escape characters for output.
434 writeData (ostream &output, const string &data)
436 for (int i = 0; i < (int)data.size(); i++) {
455 doIndent (ostream &output, int indent)
457 while (indent-- > 0) {
464 writeAtts (ostream &output, const SGPropertyNode * node, bool forceindex)
466 int index = node->getIndex();
468 if (index != 0 || forceindex)
469 output << " n=\"" << index << '"';
472 if (!node->getAttribute(SGPropertyNode::READ))
473 output << " read=\"n\"";
475 if (!node->getAttribute(SGPropertyNode::WRITE))
476 output << " write=\"n\"";
478 if (node->getAttribute(SGPropertyNode::ARCHIVE))
479 output << " archive=\"y\"";
486 * Test whether a node is archivable or has archivable descendants.
489 isArchivable (const SGPropertyNode * node, SGPropertyNode::Attribute archive_flag)
491 // FIXME: it's inefficient to do this all the time
492 if (node->getAttribute(archive_flag))
495 int nChildren = node->nChildren();
496 for (int i = 0; i < nChildren; i++)
497 if (isArchivable(node->getChild(i), archive_flag))
505 writeNode (ostream &output, const SGPropertyNode * node,
506 bool write_all, int indent, SGPropertyNode::Attribute archive_flag)
508 // Don't write the node or any of
509 // its descendants unless it is
510 // allowed to be archived.
511 if (!write_all && !isArchivable(node, archive_flag))
512 return true; // Everything's OK, but we won't write.
514 const string name = node->getName();
515 int nChildren = node->nChildren();
516 bool node_has_value = false;
518 // If there is a literal value,
520 if (node->hasValue() && (write_all || node->getAttribute(archive_flag))) {
521 doIndent(output, indent);
522 output << '<' << name;
523 writeAtts(output, node, nChildren != 0);
524 if (node->isAlias() && node->getAliasTarget() != 0) {
525 output << " alias=\"" << node->getAliasTarget()->getPath()
528 if (node->getType() != SGPropertyNode::UNSPECIFIED)
529 output << " type=\"" << getTypeName(node->getType()) << '"';
531 writeData(output, node->getStringValue());
532 output << "</" << name << '>' << endl;
534 node_has_value = true;
537 // If there are children, write them next.
539 doIndent(output, indent);
540 output << '<' << name;
541 writeAtts(output, node, node_has_value);
542 output << '>' << endl;
543 for (int i = 0; i < nChildren; i++)
544 writeNode(output, node->getChild(i), write_all, indent + INDENT_STEP, archive_flag);
545 doIndent(output, indent);
546 output << "</" << name << '>' << endl;
554 writeProperties (ostream &output, const SGPropertyNode * start_node,
555 bool write_all, SGPropertyNode::Attribute archive_flag)
557 int nChildren = start_node->nChildren();
559 output << "<?xml version=\"1.0\"?>" << endl << endl;
560 output << "<PropertyList>" << endl;
562 for (int i = 0; i < nChildren; i++) {
563 writeNode(output, start_node->getChild(i), write_all, INDENT_STEP, archive_flag);
566 output << "</PropertyList>" << endl;
571 writeProperties (const string &file, const SGPropertyNode * start_node,
572 bool write_all, SGPropertyNode::Attribute archive_flag)
574 SGPath path(file.c_str());
575 path.create_dir(0777);
577 ofstream output(file.c_str());
579 writeProperties(output, start_node, write_all, archive_flag);
581 throw sg_io_exception("Cannot open file", sg_location(file));
587 ////////////////////////////////////////////////////////////////////////
588 // Copy properties from one tree to another.
589 ////////////////////////////////////////////////////////////////////////
593 * Copy one property tree to another.
595 * @param in The source property tree.
596 * @param out The destination property tree.
597 * @return true if all properties were copied, false if some failed
598 * (for example, if the property's value is tied read-only).
601 copyProperties (const SGPropertyNode *in, SGPropertyNode *out)
605 // First, copy the actual value,
607 if (in->hasValue()) {
608 switch (in->getType()) {
609 case SGPropertyNode::BOOL:
610 if (!out->setBoolValue(in->getBoolValue()))
613 case SGPropertyNode::INT:
614 if (!out->setIntValue(in->getIntValue()))
617 case SGPropertyNode::LONG:
618 if (!out->setLongValue(in->getLongValue()))
621 case SGPropertyNode::FLOAT:
622 if (!out->setFloatValue(in->getFloatValue()))
625 case SGPropertyNode::DOUBLE:
626 if (!out->setDoubleValue(in->getDoubleValue()))
629 case SGPropertyNode::STRING:
630 if (!out->setStringValue(in->getStringValue()))
633 case SGPropertyNode::UNSPECIFIED:
634 if (!out->setUnspecifiedValue(in->getStringValue()))
640 string message = "Unknown internal SGPropertyNode type";
641 message += in->getType();
642 throw sg_error(message, "SimGear Property Reader");
646 // copy the attributes.
647 out->setAttributes( in->getAttributes() );
649 // Next, copy the children.
650 int nChildren = in->nChildren();
651 for (int i = 0; i < nChildren; i++) {
652 const SGPropertyNode * in_child = in->getChild(i);
653 SGPropertyNode * out_child = out->getChild(in_child->getName(),
654 in_child->getIndex(),
656 if (!copyProperties(in_child, out_child))
663 // end of props_io.cxx