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);
43 #define DEFAULT_MODE (SGPropertyNode::READ|SGPropertyNode::WRITE)
47 ////////////////////////////////////////////////////////////////////////
48 // Property list visitor, for XML parsing.
49 ////////////////////////////////////////////////////////////////////////
51 class PropsVisitor : public XMLVisitor
55 PropsVisitor (SGPropertyNode * root, const string &base, int default_mode = 0)
56 : _default_mode(default_mode), _root(root), _level(0), _base(base), _hasException(false) {}
58 virtual ~PropsVisitor () {}
62 void startElement (const char * name, const XMLAttributes &atts);
63 void endElement (const char * name);
64 void data (const char * s, int length);
65 void warning (const char * message, int line, int column);
67 bool hasException () const { return _hasException; }
68 sg_io_exception &getException () { return _exception; }
69 void setException (const sg_io_exception &exception) {
70 _exception = exception;
78 State () : node(0), type(""), mode(DEFAULT_MODE) {}
79 State (SGPropertyNode * _node, const char * _type, int _mode)
80 : node(_node), type(_type), mode(_mode) {}
81 SGPropertyNode * node;
84 map<string,int> counters;
87 State &state () { return _state_stack[_state_stack.size() - 1]; }
89 void push_state (SGPropertyNode * node, const char * type, int mode) {
91 _state_stack.push_back(State(node, "unspecified", mode));
93 _state_stack.push_back(State(node, type, mode));
99 _state_stack.pop_back();
105 SGPropertyNode * _root;
108 vector<State> _state_stack;
110 sg_io_exception _exception;
115 PropsVisitor::startXML ()
118 _state_stack.resize(0);
122 PropsVisitor::endXML ()
125 _state_stack.resize(0);
130 * Check a yes/no flag, with default.
133 checkFlag (const char * flag, bool defaultState = true)
137 else if (!strcmp(flag, "y"))
139 else if (!strcmp(flag, "n"))
142 string message = "Unrecognized flag value '";
145 // FIXME: add location info
146 throw sg_io_exception(message, "SimGear Property Reader");
151 PropsVisitor::startElement (const char * name, const XMLAttributes &atts)
156 if (strcmp(name, "PropertyList")) {
157 string message = "Root element name is ";
159 message += "; expected PropertyList";
160 throw sg_io_exception(message, "SimGear Property Reader");
163 // Check for an include.
164 attval = atts.getValue("include");
166 SGPath path(SGPath(_base).dir());
169 readProperties(path.str(), _root);
170 } catch (sg_io_exception &e) {
175 push_state(_root, "", DEFAULT_MODE);
181 attval = atts.getValue("n");
184 index = atoi(attval);
185 st.counters[name] = SG_MAX2(st.counters[name], index+1);
187 index = st.counters[name];
191 // Got the index, so grab the node.
192 SGPropertyNode * node = st.node->getChild(name, index, true);
193 if (!node->getAttribute(SGPropertyNode::WRITE)) {
194 SG_LOG(SG_INPUT, SG_ALERT, "Not overwriting write-protected property "
199 // Get the access-mode attributes,
200 // but don't set yet (in case they
201 // prevent us from recording the value).
202 int mode = _default_mode;
204 attval = atts.getValue("read");
205 if (checkFlag(attval, true))
206 mode |= SGPropertyNode::READ;
207 attval = atts.getValue("write");
208 if (checkFlag(attval, true))
209 mode |= SGPropertyNode::WRITE;
210 attval = atts.getValue("archive");
211 if (checkFlag(attval, false))
212 mode |= SGPropertyNode::ARCHIVE;
213 attval = atts.getValue("trace-read");
214 if (checkFlag(attval, false))
215 mode |= SGPropertyNode::TRACE_READ;
216 attval = atts.getValue("trace-write");
217 if (checkFlag(attval, false))
218 mode |= SGPropertyNode::TRACE_WRITE;
219 attval = atts.getValue("userarchive");
220 if (checkFlag(attval, false))
221 mode |= SGPropertyNode::USERARCHIVE;
223 // Check for an alias.
224 attval = atts.getValue("alias");
226 if (!node->alias(attval))
227 SG_LOG(SG_INPUT, SG_ALERT, "Failed to set alias to " << attval);
230 // Check for an include.
231 attval = atts.getValue("include");
233 SGPath path(SGPath(_base).dir());
236 readProperties(path.str(), node);
237 } catch (sg_io_exception &e) {
241 const char *omit = atts.getValue("omit-node");
242 if (omit && !strcmp(omit, "y")) {
243 int nChildren = node->nChildren();
244 for (int i = 0; i < nChildren; i++) {
245 SGPropertyNode *src = node->getChild(i);
246 const char *name = src->getName();
247 int index = st.counters[name];
249 SGPropertyNode *dst = st.node->getChild(name, index, true);
250 copyProperties(src, dst);
252 st.node->removeChild(node->getName(), node->getIndex(), false);
257 const char *type = atts.getValue("type");
260 push_state(node, type, mode);
265 PropsVisitor::endElement (const char * name)
270 // If there are no children and it's
271 // not an alias, then it's a leaf value.
272 if (st.node->nChildren() == 0 && !st.node->isAlias()) {
273 if (st.type == "bool") {
274 if (_data == "true" || atoi(_data.c_str()) != 0)
275 ret = st.node->setBoolValue(true);
277 ret = st.node->setBoolValue(false);
278 } else if (st.type == "int") {
279 ret = st.node->setIntValue(atoi(_data.c_str()));
280 } else if (st.type == "long") {
281 ret = st.node->setLongValue(strtol(_data.c_str(), 0, 0));
282 } else if (st.type == "float") {
283 ret = st.node->setFloatValue(atof(_data.c_str()));
284 } else if (st.type == "double") {
285 ret = st.node->setDoubleValue(strtod(_data.c_str(), 0));
286 } else if (st.type == "string") {
287 ret = st.node->setStringValue(_data.c_str());
288 } else if (st.type == "unspecified") {
289 ret = st.node->setUnspecifiedValue(_data.c_str());
290 } else if (_level == 1) {
291 ret = true; // empty <PropertyList>
293 string message = "Unrecognized data type '";
296 // FIXME: add location information
297 throw sg_io_exception(message, "SimGear Property Reader");
300 SG_LOG(SG_INPUT, SG_ALERT, "readProperties: Failed to set "
301 << st.node->getPath() << " to value \""
302 << _data << "\" with type " << st.type);
305 // Set the access-mode attributes now,
306 // once the value has already been
308 st.node->setAttributes(st.mode);
314 PropsVisitor::data (const char * s, int length)
316 if (state().node->nChildren() == 0)
317 _data.append(string(s, length));
321 PropsVisitor::warning (const char * message, int line, int column)
323 SG_LOG(SG_INPUT, SG_ALERT, "readProperties: warning: "
324 << message << " at line " << line << ", column " << column);
329 ////////////////////////////////////////////////////////////////////////
330 // Property list reader.
331 ////////////////////////////////////////////////////////////////////////
335 * Read properties from an input stream.
337 * @param input The input stream containing an XML property file.
338 * @param start_node The root node for reading properties.
339 * @param base A base path for resolving external include references.
340 * @return true if the read succeeded, false otherwise.
343 readProperties (istream &input, SGPropertyNode * start_node,
344 const string &base, int default_mode)
346 PropsVisitor visitor(start_node, base, default_mode);
347 readXML(input, visitor, base);
348 if (visitor.hasException())
349 throw visitor.getException();
354 * Read properties from a file.
356 * @param file A string containing the file path.
357 * @param start_node The root node for reading properties.
358 * @return true if the read succeeded, false otherwise.
361 readProperties (const string &file, SGPropertyNode * start_node,
364 PropsVisitor visitor(start_node, file, default_mode);
365 readXML(file, visitor);
366 if (visitor.hasException())
367 throw visitor.getException();
372 * Read properties from an in-memory buffer.
374 * @param buf A character buffer containing the xml data.
375 * @param size The size/length of the buffer in bytes
376 * @param start_node The root node for reading properties.
377 * @return true if the read succeeded, false otherwise.
379 void readProperties (const char *buf, const int size,
380 SGPropertyNode * start_node, int default_mode)
382 PropsVisitor visitor(start_node, "", default_mode);
383 readXML(buf, size, visitor);
384 if (visitor.hasException())
385 throw visitor.getException();
389 ////////////////////////////////////////////////////////////////////////
390 // Property list writer.
391 ////////////////////////////////////////////////////////////////////////
393 #define INDENT_STEP 2
396 * Return the type name.
399 getTypeName (SGPropertyNode::Type type)
402 case SGPropertyNode::UNSPECIFIED:
403 return "unspecified";
404 case SGPropertyNode::BOOL:
406 case SGPropertyNode::INT:
408 case SGPropertyNode::LONG:
410 case SGPropertyNode::FLOAT:
412 case SGPropertyNode::DOUBLE:
414 case SGPropertyNode::STRING:
416 case SGPropertyNode::ALIAS:
417 case SGPropertyNode::NONE:
418 return "unspecified";
421 // keep the compiler from squawking
422 return "unspecified";
427 * Escape characters for output.
430 writeData (ostream &output, const string &data)
432 for (int i = 0; i < (int)data.size(); i++) {
451 doIndent (ostream &output, int indent)
453 while (indent-- > 0) {
460 writeAtts (ostream &output, const SGPropertyNode * node, bool forceindex)
462 int index = node->getIndex();
464 if (index != 0 || forceindex)
465 output << " n=\"" << index << '"';
468 if (!node->getAttribute(SGPropertyNode::READ))
469 output << " read=\"n\"";
471 if (!node->getAttribute(SGPropertyNode::WRITE))
472 output << " write=\"n\"";
474 if (node->getAttribute(SGPropertyNode::ARCHIVE))
475 output << " archive=\"y\"";
482 * Test whether a node is archivable or has archivable descendants.
485 isArchivable (const SGPropertyNode * node, SGPropertyNode::Attribute archive_flag)
487 // FIXME: it's inefficient to do this all the time
488 if (node->getAttribute(archive_flag))
491 int nChildren = node->nChildren();
492 for (int i = 0; i < nChildren; i++)
493 if (isArchivable(node->getChild(i), archive_flag))
501 writeNode (ostream &output, const SGPropertyNode * node,
502 bool write_all, int indent, SGPropertyNode::Attribute archive_flag)
504 // Don't write the node or any of
505 // its descendants unless it is
506 // allowed to be archived.
507 if (!write_all && !isArchivable(node, archive_flag))
508 return true; // Everything's OK, but we won't write.
510 const string name = node->getName();
511 int nChildren = node->nChildren();
512 bool node_has_value = false;
514 // If there is a literal value,
516 if (node->hasValue() && (write_all || node->getAttribute(archive_flag))) {
517 doIndent(output, indent);
518 output << '<' << name;
519 writeAtts(output, node, nChildren != 0);
520 if (node->isAlias() && node->getAliasTarget() != 0) {
521 output << " alias=\"" << node->getAliasTarget()->getPath()
524 if (node->getType() != SGPropertyNode::UNSPECIFIED)
525 output << " type=\"" << getTypeName(node->getType()) << '"';
527 writeData(output, node->getStringValue());
528 output << "</" << name << '>' << endl;
530 node_has_value = true;
533 // If there are children, write them next.
535 doIndent(output, indent);
536 output << '<' << name;
537 writeAtts(output, node, node_has_value);
538 output << '>' << endl;
539 for (int i = 0; i < nChildren; i++)
540 writeNode(output, node->getChild(i), write_all, indent + INDENT_STEP, archive_flag);
541 doIndent(output, indent);
542 output << "</" << name << '>' << endl;
550 writeProperties (ostream &output, const SGPropertyNode * start_node,
551 bool write_all, SGPropertyNode::Attribute archive_flag)
553 int nChildren = start_node->nChildren();
555 output << "<?xml version=\"1.0\"?>" << endl << endl;
556 output << "<PropertyList>" << endl;
558 for (int i = 0; i < nChildren; i++) {
559 writeNode(output, start_node->getChild(i), write_all, INDENT_STEP, archive_flag);
562 output << "</PropertyList>" << endl;
567 writeProperties (const string &file, const SGPropertyNode * start_node,
568 bool write_all, SGPropertyNode::Attribute archive_flag)
570 SGPath path(file.c_str());
571 path.create_dir(0777);
573 ofstream output(file.c_str());
575 writeProperties(output, start_node, write_all, archive_flag);
577 throw sg_io_exception("Cannot open file", sg_location(file));
583 ////////////////////////////////////////////////////////////////////////
584 // Copy properties from one tree to another.
585 ////////////////////////////////////////////////////////////////////////
589 * Copy one property tree to another.
591 * @param in The source property tree.
592 * @param out The destination property tree.
593 * @return true if all properties were copied, false if some failed
594 * (for example, if the property's value is tied read-only).
597 copyProperties (const SGPropertyNode *in, SGPropertyNode *out)
601 // First, copy the actual value,
603 if (in->hasValue()) {
604 switch (in->getType()) {
605 case SGPropertyNode::BOOL:
606 if (!out->setBoolValue(in->getBoolValue()))
609 case SGPropertyNode::INT:
610 if (!out->setIntValue(in->getIntValue()))
613 case SGPropertyNode::LONG:
614 if (!out->setLongValue(in->getLongValue()))
617 case SGPropertyNode::FLOAT:
618 if (!out->setFloatValue(in->getFloatValue()))
621 case SGPropertyNode::DOUBLE:
622 if (!out->setDoubleValue(in->getDoubleValue()))
625 case SGPropertyNode::STRING:
626 if (!out->setStringValue(in->getStringValue()))
629 case SGPropertyNode::UNSPECIFIED:
630 if (!out->setUnspecifiedValue(in->getStringValue()))
636 string message = "Unknown internal SGPropertyNode type";
637 message += in->getType();
638 throw sg_error(message, "SimGear Property Reader");
642 // copy the attributes.
643 out->setAttributes( in->getAttributes() );
645 // Next, copy the children.
646 int nChildren = in->nChildren();
647 for (int i = 0; i < nChildren; i++) {
648 const SGPropertyNode * in_child = in->getChild(i);
649 SGPropertyNode * out_child = out->getChild(in_child->getName(),
650 in_child->getIndex(),
652 if (!copyProperties(in_child, out_child))
659 // end of props_io.cxx