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)
146 if (string(name) != (string)"PropertyList") {
147 string message = "Root element name is ";
149 message += "; expected PropertyList";
150 throw sg_io_exception(message, "SimGear Property Reader");
153 // Check for an include.
154 attval = atts.getValue("include");
156 SGPath path(SGPath(_base).dir());
159 readProperties(path.str(), _root);
160 } catch (sg_io_exception &e) {
165 push_state(_root, "", DEFAULT_MODE);
170 attval = atts.getValue("n");
173 index = atoi(attval);
174 st.counters[name] = SG_MAX2(st.counters[name], index+1);
176 index = st.counters[name];
180 // Got the index, so grab the node.
181 SGPropertyNode * node = st.node->getChild(name, index, true);
183 // Get the access-mode attributes,
184 // but don't set yet (in case they
185 // prevent us from recording the value).
188 attval = atts.getValue("read");
189 if (checkFlag(attval, true))
190 mode |= SGPropertyNode::READ;
191 attval = atts.getValue("write");
192 if (checkFlag(attval, true))
193 mode |= SGPropertyNode::WRITE;
194 attval = atts.getValue("archive");
195 if (checkFlag(attval, false))
196 mode |= SGPropertyNode::ARCHIVE;
197 attval = atts.getValue("trace-read");
198 if (checkFlag(attval, false))
199 mode |= SGPropertyNode::TRACE_READ;
200 attval = atts.getValue("trace-write");
201 if (checkFlag(attval, false))
202 mode |= SGPropertyNode::TRACE_WRITE;
204 // Check for an alias.
205 attval = atts.getValue("alias");
207 if (!node->alias(attval))
208 SG_LOG(SG_INPUT, SG_ALERT, "Failed to set alias to " << attval);
211 // Check for an include.
212 attval = atts.getValue("include");
214 SGPath path(SGPath(_base).dir());
217 readProperties(path.str(), node);
218 } catch (sg_io_exception &e) {
223 push_state(node, atts.getValue("type"), mode);
228 PropsVisitor::endElement (const char * name)
233 // If there are no children and it's
234 // not an alias, then it's a leaf value.
235 if (st.node->nChildren() == 0 && !st.node->isAlias()) {
236 if (st.type == "bool") {
237 if (_data == "true" || atoi(_data.c_str()) != 0)
238 ret = st.node->setBoolValue(true);
240 ret = st.node->setBoolValue(false);
241 } else if (st.type == "int") {
242 ret = st.node->setIntValue(atoi(_data.c_str()));
243 } else if (st.type == "long") {
244 ret = st.node->setLongValue(strtol(_data.c_str(), 0, 0));
245 } else if (st.type == "float") {
246 ret = st.node->setFloatValue(atof(_data.c_str()));
247 } else if (st.type == "double") {
248 ret = st.node->setDoubleValue(strtod(_data.c_str(), 0));
249 } else if (st.type == "string") {
250 ret = st.node->setStringValue(_data.c_str());
251 } else if (st.type == "unspecified") {
252 ret = st.node->setUnspecifiedValue(_data.c_str());
254 string message = "Unrecognized data type '";
257 // FIXME: add location information
258 throw sg_io_exception(message, "SimGear Property Reader");
261 SG_LOG(SG_INPUT, SG_ALERT, "readProperties: Failed to set "
262 << st.node->getPath() << " to value \""
263 << _data << "\" with type " << st.type);
266 // Set the access-mode attributes now,
267 // once the value has already been
269 st.node->setAttributes(st.mode);
275 PropsVisitor::data (const char * s, int length)
277 if (state().node->nChildren() == 0)
278 _data.append(string(s, length));
282 PropsVisitor::warning (const char * message, int line, int column)
284 SG_LOG(SG_INPUT, SG_ALERT, "readProperties: warning: "
285 << message << " at line " << line << ", column " << column);
290 ////////////////////////////////////////////////////////////////////////
291 // Property list reader.
292 ////////////////////////////////////////////////////////////////////////
296 * Read properties from an input stream.
298 * @param input The input stream containing an XML property file.
299 * @param start_node The root node for reading properties.
300 * @param base A base path for resolving external include references.
301 * @return true if the read succeeded, false otherwise.
304 readProperties (istream &input, SGPropertyNode * start_node,
307 PropsVisitor visitor(start_node, base);
308 readXML(input, visitor, base);
309 if (visitor.hasException())
310 throw visitor.getException();
315 * Read properties from a file.
317 * @param file A string containing the file path.
318 * @param start_node The root node for reading properties.
319 * @return true if the read succeeded, false otherwise.
322 readProperties (const string &file, SGPropertyNode * start_node)
324 PropsVisitor visitor(start_node, file);
325 readXML(file, visitor);
326 if (visitor.hasException())
327 throw visitor.getException();
332 ////////////////////////////////////////////////////////////////////////
333 // Property list writer.
334 ////////////////////////////////////////////////////////////////////////
336 #define INDENT_STEP 2
339 * Return the type name.
342 getTypeName (SGPropertyNode::Type type)
345 case SGPropertyNode::UNSPECIFIED:
346 return "unspecified";
347 case SGPropertyNode::BOOL:
349 case SGPropertyNode::INT:
351 case SGPropertyNode::LONG:
353 case SGPropertyNode::FLOAT:
355 case SGPropertyNode::DOUBLE:
357 case SGPropertyNode::STRING:
359 case SGPropertyNode::ALIAS:
360 case SGPropertyNode::NONE:
361 return "unspecified";
364 // keep the compiler from squawking
365 return "unspecified";
370 * Escape characters for output.
373 writeData (ostream &output, const string &data)
375 for (int i = 0; i < (int)data.size(); i++) {
394 doIndent (ostream &output, int indent)
396 while (indent-- > 0) {
403 writeAtts (ostream &output, const SGPropertyNode * node)
405 int index = node->getIndex();
408 output << " n=\"" << index << '"';
411 if (!node->getAttribute(SGPropertyNode::READ))
412 output << " read=\"n\"";
414 if (!node->getAttribute(SGPropertyNode::WRITE))
415 output << " write=\"n\"";
417 if (node->getAttribute(SGPropertyNode::ARCHIVE))
418 output << " archive=\"y\"";
425 * Test whether a node is archivable or has archivable descendants.
428 isArchivable (const SGPropertyNode * node)
430 // FIXME: it's inefficient to do this all the time
431 if (node->getAttribute(SGPropertyNode::ARCHIVE))
434 int nChildren = node->nChildren();
435 for (int i = 0; i < nChildren; i++)
436 if (isArchivable(node->getChild(i)))
444 writeNode (ostream &output, const SGPropertyNode * node,
445 bool write_all, int indent)
447 // Don't write the node or any of
448 // its descendants unless it is
449 // allowed to be archived.
450 if (!write_all && !isArchivable(node))
451 return true; // Everything's OK, but we won't write.
453 const string name = node->getName();
454 int nChildren = node->nChildren();
456 // If there is a literal value,
458 if (node->hasValue() && (write_all || node->getAttribute(SGPropertyNode::ARCHIVE))) {
459 doIndent(output, indent);
460 output << '<' << name;
461 writeAtts(output, node);
462 if (node->isAlias() && node->getAliasTarget() != 0) {
463 output << " alias=\"" << node->getAliasTarget()->getPath()
466 if (node->getType() != SGPropertyNode::UNSPECIFIED)
467 output << " type=\"" << getTypeName(node->getType()) << '"';
469 writeData(output, node->getStringValue());
470 output << "</" << name << '>' << endl;
474 // If there are children, write them next.
475 if (nChildren > 0 || node->isAlias()) {
476 doIndent(output, indent);
477 output << '<' << name;
478 writeAtts(output, node);
479 output << '>' << endl;
480 for (int i = 0; i < nChildren; i++)
481 writeNode(output, node->getChild(i), write_all, indent + INDENT_STEP);
482 doIndent(output, indent);
483 output << "</" << name << '>' << endl;
491 writeProperties (ostream &output, const SGPropertyNode * start_node,
494 int nChildren = start_node->nChildren();
496 output << "<?xml version=\"1.0\"?>" << endl << endl;
497 output << "<PropertyList>" << endl;
499 for (int i = 0; i < nChildren; i++) {
500 writeNode(output, start_node->getChild(i), write_all, INDENT_STEP);
503 output << "</PropertyList>" << endl;
508 writeProperties (const string &file, const SGPropertyNode * start_node,
511 ofstream output(file.c_str());
513 writeProperties(output, start_node, write_all);
515 throw sg_io_exception("Cannot open file", sg_location(file));
521 ////////////////////////////////////////////////////////////////////////
522 // Copy properties from one tree to another.
523 ////////////////////////////////////////////////////////////////////////
527 * Copy one property tree to another.
529 * @param in The source property tree.
530 * @param out The destination property tree.
531 * @return true if all properties were copied, false if some failed
532 * (for example, if the property's value is tied read-only).
535 copyProperties (const SGPropertyNode *in, SGPropertyNode *out)
539 // First, copy the actual value,
541 if (in->hasValue()) {
542 switch (in->getType()) {
543 case SGPropertyNode::BOOL:
544 if (!out->setBoolValue(in->getBoolValue()))
547 case SGPropertyNode::INT:
548 if (!out->setIntValue(in->getIntValue()))
551 case SGPropertyNode::LONG:
552 if (!out->setLongValue(in->getLongValue()))
555 case SGPropertyNode::FLOAT:
556 if (!out->setFloatValue(in->getFloatValue()))
559 case SGPropertyNode::DOUBLE:
560 if (!out->setDoubleValue(in->getDoubleValue()))
563 case SGPropertyNode::STRING:
564 if (!out->setStringValue(in->getStringValue()))
567 case SGPropertyNode::UNSPECIFIED:
568 if (!out->setUnspecifiedValue(in->getStringValue()))
572 string message = "Unknown internal SGPropertyNode type";
573 message += in->getType();
574 throw sg_error(message, "SimGear Property Reader");
578 // Next, copy the children.
579 int nChildren = in->nChildren();
580 for (int i = 0; i < nChildren; i++) {
581 const SGPropertyNode * in_child = in->getChild(i);
582 SGPropertyNode * out_child = out->getChild(in_child->getName(),
583 in_child->getIndex(),
585 if (!copyProperties(in_child, out_child))
592 // end of props_io.cxx