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"
14 #if !defined(SG_HAVE_NATIVE_SGI_COMPILERS)
23 #if !defined(SG_HAVE_NATIVE_SGI_COMPILERS)
24 SG_USING_STD(istream);
25 SG_USING_STD(ifstream);
26 SG_USING_STD(ostream);
27 SG_USING_STD(ofstream);
33 #define DEFAULT_MODE (SGPropertyNode::READ|SGPropertyNode::WRITE)
37 ////////////////////////////////////////////////////////////////////////
38 // Property list visitor, for XML parsing.
39 ////////////////////////////////////////////////////////////////////////
41 class PropsVisitor : public XMLVisitor
45 PropsVisitor (SGPropertyNode * root, const string &base)
46 : _root(root), _level(0), _base(base), _exception(0) {}
48 virtual ~PropsVisitor () { delete _exception; }
52 void startElement (const char * name, const XMLAttributes &atts);
53 void endElement (const char * name);
54 void data (const char * s, int length);
55 void warning (const char * message, int line, int column);
57 bool hasException () const { return (_exception != 0); }
58 const sg_io_exception * getException () const { return _exception; }
64 State () : node(0), type(""), mode(DEFAULT_MODE) {}
65 State (SGPropertyNode * _node, const char * _type, int _mode)
66 : node(_node), type(_type), mode(_mode) {}
67 SGPropertyNode * node;
70 map<string,int> counters;
73 State &state () { return _state_stack[_state_stack.size() - 1]; }
75 void push_state (SGPropertyNode * node, const char * type, int mode) {
77 _state_stack.push_back(State(node, "unspecified", mode));
79 _state_stack.push_back(State(node, type, mode));
85 _state_stack.pop_back();
90 SGPropertyNode * _root;
92 vector<State> _state_stack;
94 sg_io_exception * _exception;
98 PropsVisitor::startXML ()
101 _state_stack.resize(0);
105 PropsVisitor::endXML ()
108 _state_stack.resize(0);
113 * Check a yes/no flag, with default.
116 checkFlag (const char * flag, bool defaultState = true)
120 else if (string(flag) == "y")
122 else if (string(flag) == "n")
125 string message = "Unrecognized flag value '";
128 // FIXME: add location info
129 throw sg_io_exception(message, "SimGear Property Reader");
134 PropsVisitor::startElement (const char * name, const XMLAttributes &atts)
139 if (string(name) != (string)"PropertyList") {
140 string message = "Root element name is ";
142 message += "; expected PropertyList";
143 throw sg_io_exception(message, "SimGear Property Reader");
145 push_state(_root, "", DEFAULT_MODE);
152 attval = atts.getValue("n");
155 index = atoi(attval);
156 st.counters[name] = SG_MAX2(st.counters[name], index+1);
158 index = st.counters[name];
162 // Got the index, so grab the node.
163 SGPropertyNode * node = st.node->getChild(name, index, true);
165 // Get the access-mode attributes,
166 // but don't set yet (in case they
167 // prevent us from recording the value).
170 attval = atts.getValue("read");
171 if (checkFlag(attval, true))
172 mode |= SGPropertyNode::READ;
173 attval = atts.getValue("write");
174 if (checkFlag(attval, true))
175 mode |= SGPropertyNode::WRITE;
176 attval = atts.getValue("archive");
177 if (checkFlag(attval, false))
178 mode |= SGPropertyNode::ARCHIVE;
180 // Check for an alias.
181 attval = atts.getValue("alias");
183 if (!node->alias(attval))
184 SG_LOG(SG_INPUT, SG_ALERT, "Failed to set alias to " << attval);
187 // Check for an include.
188 attval = atts.getValue("include");
190 SGPath path(SGPath(_base).dir());
193 readProperties(path.str(), node);
194 } catch (const sg_io_exception &t) {
195 cerr << "Caught exception\n";
196 _exception = t.clone();
200 push_state(node, atts.getValue("type"), mode);
205 PropsVisitor::endElement (const char * name)
210 // If there are no children and it's
211 // not an alias, then it's a leaf value.
212 if (st.node->nChildren() == 0 && !st.node->isAlias()) {
213 if (st.type == "bool") {
214 if (_data == "true" || atoi(_data.c_str()) != 0)
215 ret = st.node->setBoolValue(true);
217 ret = st.node->setBoolValue(false);
218 } else if (st.type == "int") {
219 ret = st.node->setIntValue(atoi(_data.c_str()));
220 } else if (st.type == "long") {
221 ret = st.node->setLongValue(strtol(_data.c_str(), 0, 0));
222 } else if (st.type == "float") {
223 ret = st.node->setFloatValue(atof(_data.c_str()));
224 } else if (st.type == "double") {
225 ret = st.node->setDoubleValue(strtod(_data.c_str(), 0));
226 } else if (st.type == "string") {
227 ret = st.node->setStringValue(_data);
228 } else if (st.type == "unspecified") {
229 ret = st.node->setUnspecifiedValue(_data);
231 string message = "Unrecognized data type '";
234 // FIXME: add location information
235 throw sg_io_exception(message, "SimGear Property Reader");
238 SG_LOG(SG_INPUT, SG_ALERT, "readProperties: Failed to set "
239 << st.node->getPath() << " to value \""
240 << _data << "\" with type " << st.type);
243 // Set the access-mode attributes now,
244 // once the value has already been
246 st.node->setAttributes(st.mode);
252 PropsVisitor::data (const char * s, int length)
254 if (state().node->nChildren() == 0)
255 _data.append(string(s, length));
259 PropsVisitor::warning (const char * message, int line, int column)
261 SG_LOG(SG_INPUT, SG_ALERT, "readProperties: warning: "
262 << message << " at line " << line << ", column " << column);
267 ////////////////////////////////////////////////////////////////////////
268 // Property list reader.
269 ////////////////////////////////////////////////////////////////////////
273 * Read properties from an input stream.
275 * @param input The input stream containing an XML property file.
276 * @param start_node The root node for reading properties.
277 * @param base A base path for resolving external include references.
278 * @return true if the read succeeded, false otherwise.
281 readProperties (istream &input, SGPropertyNode * start_node,
284 PropsVisitor visitor(start_node, base);
285 readXML(input, visitor, base);
286 if (visitor.hasException())
287 throw *(visitor.getException());
292 * Read properties from a file.
294 * @param file A string containing the file path.
295 * @param start_node The root node for reading properties.
296 * @return true if the read succeeded, false otherwise.
299 readProperties (const string &file, SGPropertyNode * start_node)
301 PropsVisitor visitor(start_node, file);
302 readXML(file, visitor);
303 if (visitor.hasException())
304 throw *(visitor.getException());
309 ////////////////////////////////////////////////////////////////////////
310 // Property list writer.
311 ////////////////////////////////////////////////////////////////////////
313 #define INDENT_STEP 2
316 * Return the type name.
319 getTypeName (SGPropertyNode::Type type)
322 case SGPropertyNode::UNSPECIFIED:
323 return "unspecified";
324 case SGPropertyNode::BOOL:
326 case SGPropertyNode::INT:
328 case SGPropertyNode::LONG:
330 case SGPropertyNode::FLOAT:
332 case SGPropertyNode::DOUBLE:
334 case SGPropertyNode::STRING:
338 // keep the compiler from squawking
339 return "unspecified";
344 * Escape characters for output.
347 writeData (ostream &output, const string &data)
349 for (int i = 0; i < (int)data.size(); i++) {
368 doIndent (ostream &output, int indent)
370 while (indent-- > 0) {
377 writeAtts (ostream &output, const SGPropertyNode * node)
379 int index = node->getIndex();
382 output << " n=\"" << index << '"';
385 if (!node->getAttribute(SGPropertyNode::READ))
386 output << " read=\"n\"";
388 if (!node->getAttribute(SGPropertyNode::WRITE))
389 output << " write=\"n\"";
391 if (node->getAttribute(SGPropertyNode::ARCHIVE))
392 output << " archive=\"y\"";
399 * Test whether a node is archivable or has archivable descendants.
402 isArchivable (const SGPropertyNode * node)
404 // FIXME: it's inefficient to do this all the time
405 if (node->getAttribute(SGPropertyNode::ARCHIVE))
408 int nChildren = node->nChildren();
409 for (int i = 0; i < nChildren; i++)
410 if (isArchivable(node->getChild(i)))
418 writeNode (ostream &output, const SGPropertyNode * node, int indent)
420 // Don't write the node or any of
421 // its descendants unless it is
422 // allowed to be archived.
423 if (!isArchivable(node))
424 return true; // Everything's OK, but we won't write.
426 const string &name = node->getName();
427 int index = node->getIndex();
428 int nChildren = node->nChildren();
430 // If there is a literal value,
432 if (node->hasValue() && node->getAttribute(SGPropertyNode::ARCHIVE)) {
433 doIndent(output, indent);
434 output << '<' << name;
435 writeAtts(output, node);
436 if (node->isAlias() && node->getAliasTarget() != 0) {
437 output << " alias=\"" << node->getAliasTarget()->getPath()
440 if (node->getType() != SGPropertyNode::UNSPECIFIED)
441 output << " type=\"" << getTypeName(node->getType()) << '"';
443 writeData(output, node->getStringValue());
444 output << "</" << name << '>' << endl;
448 // If there are children, write them
450 if (nChildren > 0 || node->isAlias()) {
451 doIndent(output, indent);
452 output << '<' << name;
453 writeAtts(output, node);
454 output << '>' << endl;
455 for (int i = 0; i < nChildren; i++)
456 writeNode(output, node->getChild(i), indent + INDENT_STEP);
457 doIndent(output, indent);
458 output << "</" << name << '>' << endl;
466 writeProperties (ostream &output, const SGPropertyNode * start_node)
468 int nChildren = start_node->nChildren();
470 output << "<?xml version=\"1.0\"?>" << endl << endl;
471 output << "<PropertyList>" << endl;
473 for (int i = 0; i < nChildren; i++) {
474 writeNode(output, start_node->getChild(i), INDENT_STEP);
477 output << "</PropertyList>" << endl;
482 writeProperties (const string &file, const SGPropertyNode * start_node)
484 ofstream output(file.c_str());
486 writeProperties(output, start_node);
488 throw sg_io_exception("Cannot open file", sg_location(file));
494 ////////////////////////////////////////////////////////////////////////
495 // Copy properties from one tree to another.
496 ////////////////////////////////////////////////////////////////////////
500 * Copy one property tree to another.
502 * @param in The source property tree.
503 * @param out The destination property tree.
504 * @return true if all properties were copied, false if some failed
505 * (for example, if the property's value is tied read-only).
508 copyProperties (const SGPropertyNode *in, SGPropertyNode *out)
512 // First, copy the actual value,
514 if (in->hasValue()) {
515 switch (in->getType()) {
516 case SGPropertyNode::BOOL:
517 if (!out->setBoolValue(in->getBoolValue()))
520 case SGPropertyNode::INT:
521 if (!out->setIntValue(in->getIntValue()))
524 case SGPropertyNode::LONG:
525 if (!out->setLongValue(in->getLongValue()))
528 case SGPropertyNode::FLOAT:
529 if (!out->setFloatValue(in->getFloatValue()))
532 case SGPropertyNode::DOUBLE:
533 if (!out->setDoubleValue(in->getDoubleValue()))
536 case SGPropertyNode::STRING:
537 if (!out->setStringValue(in->getStringValue()))
540 case SGPropertyNode::UNSPECIFIED:
541 if (!out->setUnspecifiedValue(in->getStringValue()))
545 string message = "Unknown internal SGPropertyNode type";
546 message += in->getType();
547 throw sg_error(message, "SimGear Property Reader");
551 // Next, copy the children.
552 int nChildren = in->nChildren();
553 for (int i = 0; i < nChildren; i++) {
554 const SGPropertyNode * in_child = in->getChild(i);
555 SGPropertyNode * out_child = out->getChild(in_child->getName(),
556 in_child->getIndex(),
558 if (!copyProperties(in_child, out_child))
565 // end of props_io.cxx