2 #include <simgear/compiler.h>
4 #include <stdlib.h> // atof() atoi()
6 #include <simgear/sg_inlines.h>
7 #include <simgear/debug/logstream.hxx>
8 #include <simgear/xml/easyxml.hxx>
10 #include "sg_path.hxx"
12 #include "props_io.hxx"
15 #if !defined(SG_HAVE_NATIVE_SGI_COMPILERS)
24 #if !defined(SG_HAVE_NATIVE_SGI_COMPILERS)
25 SG_USING_STD(istream);
26 SG_USING_STD(ifstream);
27 SG_USING_STD(ostream);
28 SG_USING_STD(ofstream);
34 #define DEFAULT_MODE (SGPropertyNode::READ|SGPropertyNode::WRITE)
38 ////////////////////////////////////////////////////////////////////////
39 // Property list visitor, for XML parsing.
40 ////////////////////////////////////////////////////////////////////////
42 class PropsVisitor : public XMLVisitor
46 PropsVisitor (SGPropertyNode * root, const string &base)
47 : _root(root), _level(0), _base(base), _hasException(false) {}
49 virtual ~PropsVisitor () {}
53 void startElement (const char * name, const XMLAttributes &atts);
54 void endElement (const char * name);
55 void data (const char * s, int length);
56 void warning (const char * message, int line, int column);
58 bool hasException () const { return _hasException; }
59 sg_io_exception &getException () { return _exception; }
60 void setException (const sg_io_exception &exception) {
61 _exception = exception;
69 State () : node(0), type(""), mode(DEFAULT_MODE) {}
70 State (SGPropertyNode * _node, const char * _type, int _mode)
71 : node(_node), type(_type), mode(_mode) {}
72 SGPropertyNode * node;
75 map<string,int> counters;
78 State &state () { return _state_stack[_state_stack.size() - 1]; }
80 void push_state (SGPropertyNode * node, const char * type, int mode) {
82 _state_stack.push_back(State(node, "unspecified", mode));
84 _state_stack.push_back(State(node, type, mode));
90 _state_stack.pop_back();
95 SGPropertyNode * _root;
97 vector<State> _state_stack;
99 sg_io_exception _exception;
104 PropsVisitor::startXML ()
107 _state_stack.resize(0);
111 PropsVisitor::endXML ()
114 _state_stack.resize(0);
119 * Check a yes/no flag, with default.
122 checkFlag (const char * flag, bool defaultState = true)
126 else if (string(flag) == "y")
128 else if (string(flag) == "n")
131 string message = "Unrecognized flag value '";
134 // FIXME: add location info
135 throw sg_io_exception(message, "SimGear Property Reader");
140 PropsVisitor::startElement (const char * name, const XMLAttributes &atts)
145 if (string(name) != (string)"PropertyList") {
146 string message = "Root element name is ";
148 message += "; expected PropertyList";
149 throw sg_io_exception(message, "SimGear Property Reader");
151 push_state(_root, "", DEFAULT_MODE);
158 attval = atts.getValue("n");
161 index = atoi(attval);
162 st.counters[name] = SG_MAX2(st.counters[name], index+1);
164 index = st.counters[name];
168 // Got the index, so grab the node.
169 SGPropertyNode * node = st.node->getChild(name, index, true);
171 // Get the access-mode attributes,
172 // but don't set yet (in case they
173 // prevent us from recording the value).
176 attval = atts.getValue("read");
177 if (checkFlag(attval, true))
178 mode |= SGPropertyNode::READ;
179 attval = atts.getValue("write");
180 if (checkFlag(attval, true))
181 mode |= SGPropertyNode::WRITE;
182 attval = atts.getValue("archive");
183 if (checkFlag(attval, false))
184 mode |= SGPropertyNode::ARCHIVE;
185 attval = atts.getValue("trace-read");
186 if (checkFlag(attval, false))
187 mode |= SGPropertyNode::TRACE_READ;
188 attval = atts.getValue("trace-write");
189 if (checkFlag(attval, false))
190 mode |= SGPropertyNode::TRACE_WRITE;
192 // Check for an alias.
193 attval = atts.getValue("alias");
195 if (!node->alias(attval))
196 SG_LOG(SG_INPUT, SG_ALERT, "Failed to set alias to " << attval);
199 // Check for an include.
200 attval = atts.getValue("include");
202 SGPath path(SGPath(_base).dir());
205 readProperties(path.str(), node);
206 } catch (sg_io_exception &e) {
211 push_state(node, atts.getValue("type"), mode);
216 PropsVisitor::endElement (const char * name)
221 // If there are no children and it's
222 // not an alias, then it's a leaf value.
223 if (st.node->nChildren() == 0 && !st.node->isAlias()) {
224 if (st.type == "bool") {
225 if (_data == "true" || atoi(_data.c_str()) != 0)
226 ret = st.node->setBoolValue(true);
228 ret = st.node->setBoolValue(false);
229 } else if (st.type == "int") {
230 ret = st.node->setIntValue(atoi(_data.c_str()));
231 } else if (st.type == "long") {
232 ret = st.node->setLongValue(strtol(_data.c_str(), 0, 0));
233 } else if (st.type == "float") {
234 ret = st.node->setFloatValue(atof(_data.c_str()));
235 } else if (st.type == "double") {
236 ret = st.node->setDoubleValue(strtod(_data.c_str(), 0));
237 } else if (st.type == "string") {
238 ret = st.node->setStringValue(_data.c_str());
239 } else if (st.type == "unspecified") {
240 ret = st.node->setUnspecifiedValue(_data.c_str());
242 string message = "Unrecognized data type '";
245 // FIXME: add location information
246 throw sg_io_exception(message, "SimGear Property Reader");
249 SG_LOG(SG_INPUT, SG_ALERT, "readProperties: Failed to set "
250 << st.node->getPath() << " to value \""
251 << _data << "\" with type " << st.type);
254 // Set the access-mode attributes now,
255 // once the value has already been
257 st.node->setAttributes(st.mode);
263 PropsVisitor::data (const char * s, int length)
265 if (state().node->nChildren() == 0)
266 _data.append(string(s, length));
270 PropsVisitor::warning (const char * message, int line, int column)
272 SG_LOG(SG_INPUT, SG_ALERT, "readProperties: warning: "
273 << message << " at line " << line << ", column " << column);
278 ////////////////////////////////////////////////////////////////////////
279 // Property list reader.
280 ////////////////////////////////////////////////////////////////////////
284 * Read properties from an input stream.
286 * @param input The input stream containing an XML property file.
287 * @param start_node The root node for reading properties.
288 * @param base A base path for resolving external include references.
289 * @return true if the read succeeded, false otherwise.
292 readProperties (istream &input, SGPropertyNode * start_node,
295 PropsVisitor visitor(start_node, base);
296 readXML(input, visitor, base);
297 if (visitor.hasException())
298 throw visitor.getException();
303 * Read properties from a file.
305 * @param file A string containing the file path.
306 * @param start_node The root node for reading properties.
307 * @return true if the read succeeded, false otherwise.
310 readProperties (const string &file, SGPropertyNode * start_node)
312 PropsVisitor visitor(start_node, file);
313 readXML(file, visitor);
314 if (visitor.hasException())
315 throw visitor.getException();
320 ////////////////////////////////////////////////////////////////////////
321 // Property list writer.
322 ////////////////////////////////////////////////////////////////////////
324 #define INDENT_STEP 2
327 * Return the type name.
330 getTypeName (SGPropertyNode::Type type)
333 case SGPropertyNode::UNSPECIFIED:
334 return "unspecified";
335 case SGPropertyNode::BOOL:
337 case SGPropertyNode::INT:
339 case SGPropertyNode::LONG:
341 case SGPropertyNode::FLOAT:
343 case SGPropertyNode::DOUBLE:
345 case SGPropertyNode::STRING:
347 case SGPropertyNode::ALIAS:
348 case SGPropertyNode::NONE:
349 return "unspecified";
352 // keep the compiler from squawking
353 return "unspecified";
358 * Escape characters for output.
361 writeData (ostream &output, const string &data)
363 for (int i = 0; i < (int)data.size(); i++) {
382 doIndent (ostream &output, int indent)
384 while (indent-- > 0) {
391 writeAtts (ostream &output, const SGPropertyNode * node)
393 int index = node->getIndex();
396 output << " n=\"" << index << '"';
399 if (!node->getAttribute(SGPropertyNode::READ))
400 output << " read=\"n\"";
402 if (!node->getAttribute(SGPropertyNode::WRITE))
403 output << " write=\"n\"";
405 if (node->getAttribute(SGPropertyNode::ARCHIVE))
406 output << " archive=\"y\"";
413 * Test whether a node is archivable or has archivable descendants.
416 isArchivable (const SGPropertyNode * node)
418 // FIXME: it's inefficient to do this all the time
419 if (node->getAttribute(SGPropertyNode::ARCHIVE))
422 int nChildren = node->nChildren();
423 for (int i = 0; i < nChildren; i++)
424 if (isArchivable(node->getChild(i)))
432 writeNode (ostream &output, const SGPropertyNode * node,
433 bool write_all, int indent)
435 // Don't write the node or any of
436 // its descendants unless it is
437 // allowed to be archived.
438 if (!write_all && !isArchivable(node))
439 return true; // Everything's OK, but we won't write.
441 const string name = node->getName();
442 int nChildren = node->nChildren();
444 // If there is a literal value,
446 if (node->hasValue() && (write_all || node->getAttribute(SGPropertyNode::ARCHIVE))) {
447 doIndent(output, indent);
448 output << '<' << name;
449 writeAtts(output, node);
450 if (node->isAlias() && node->getAliasTarget() != 0) {
451 output << " alias=\"" << node->getAliasTarget()->getPath()
454 if (node->getType() != SGPropertyNode::UNSPECIFIED)
455 output << " type=\"" << getTypeName(node->getType()) << '"';
457 writeData(output, node->getStringValue());
458 output << "</" << name << '>' << endl;
462 // If there are children, write them next.
463 if (nChildren > 0 || node->isAlias()) {
464 doIndent(output, indent);
465 output << '<' << name;
466 writeAtts(output, node);
467 output << '>' << endl;
468 for (int i = 0; i < nChildren; i++)
469 writeNode(output, node->getChild(i), write_all, indent + INDENT_STEP);
470 doIndent(output, indent);
471 output << "</" << name << '>' << endl;
479 writeProperties (ostream &output, const SGPropertyNode * start_node,
482 int nChildren = start_node->nChildren();
484 output << "<?xml version=\"1.0\"?>" << endl << endl;
485 output << "<PropertyList>" << endl;
487 for (int i = 0; i < nChildren; i++) {
488 writeNode(output, start_node->getChild(i), write_all, INDENT_STEP);
491 output << "</PropertyList>" << endl;
496 writeProperties (const string &file, const SGPropertyNode * start_node,
499 ofstream output(file.c_str());
501 writeProperties(output, start_node, write_all);
503 throw sg_io_exception("Cannot open file", sg_location(file));
509 ////////////////////////////////////////////////////////////////////////
510 // Copy properties from one tree to another.
511 ////////////////////////////////////////////////////////////////////////
515 * Copy one property tree to another.
517 * @param in The source property tree.
518 * @param out The destination property tree.
519 * @return true if all properties were copied, false if some failed
520 * (for example, if the property's value is tied read-only).
523 copyProperties (const SGPropertyNode *in, SGPropertyNode *out)
527 // First, copy the actual value,
529 if (in->hasValue()) {
530 switch (in->getType()) {
531 case SGPropertyNode::BOOL:
532 if (!out->setBoolValue(in->getBoolValue()))
535 case SGPropertyNode::INT:
536 if (!out->setIntValue(in->getIntValue()))
539 case SGPropertyNode::LONG:
540 if (!out->setLongValue(in->getLongValue()))
543 case SGPropertyNode::FLOAT:
544 if (!out->setFloatValue(in->getFloatValue()))
547 case SGPropertyNode::DOUBLE:
548 if (!out->setDoubleValue(in->getDoubleValue()))
551 case SGPropertyNode::STRING:
552 if (!out->setStringValue(in->getStringValue()))
555 case SGPropertyNode::UNSPECIFIED:
556 if (!out->setUnspecifiedValue(in->getStringValue()))
560 string message = "Unknown internal SGPropertyNode type";
561 message += in->getType();
562 throw sg_error(message, "SimGear Property Reader");
566 // Next, copy the children.
567 int nChildren = in->nChildren();
568 for (int i = 0; i < nChildren; i++) {
569 const SGPropertyNode * in_child = in->getChild(i);
570 SGPropertyNode * out_child = out->getChild(in_child->getName(),
571 in_child->getIndex(),
573 if (!copyProperties(in_child, out_child))
580 // end of props_io.cxx