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 : _ok(true), _root(root), _level(0), _base(base) {}
50 void startElement (const char * name, const XMLAttributes &atts);
51 void endElement (const char * name);
52 void data (const char * s, int length);
53 void warning (const char * message, int line, int column);
54 void error (const char * message, int line, int column);
56 bool isOK () const { return _ok; }
62 State () : node(0), type(""), mode(DEFAULT_MODE) {}
63 State (SGPropertyNode * _node, const char * _type, int _mode)
64 : node(_node), type(_type), mode(_mode) {}
65 SGPropertyNode * node;
68 map<string,int> counters;
71 State &state () { return _state_stack[_state_stack.size() - 1]; }
73 void push_state (SGPropertyNode * node, const char * type, int mode) {
75 _state_stack.push_back(State(node, "unspecified", mode));
77 _state_stack.push_back(State(node, type, mode));
83 _state_stack.pop_back();
89 SGPropertyNode * _root;
91 vector<State> _state_stack;
96 PropsVisitor::startXML ()
99 _state_stack.resize(0);
103 PropsVisitor::endXML ()
106 _state_stack.resize(0);
111 * Check a yes/no flag, with default.
114 checkFlag (const char * flag, bool defaultState = true)
118 else if (string(flag) == "y")
120 else if (string(flag) == "n")
123 SG_LOG(SG_INPUT, SG_ALERT, "Unrecognized flag value '" << flag
124 << "', assuming yes");
130 PropsVisitor::startElement (const char * name, const XMLAttributes &atts)
135 if (string(name) != (string)"PropertyList") {
136 SG_LOG(SG_INPUT, SG_ALERT, "Root element name is " <<
137 name << "; expected PropertyList");
140 push_state(_root, "", DEFAULT_MODE);
147 attval = atts.getValue("n");
150 index = atoi(attval);
151 st.counters[name] = SG_MAX2(st.counters[name], index+1);
153 index = st.counters[name];
157 // Got the index, so grab the node.
158 SGPropertyNode * node = st.node->getChild(name, index, true);
160 // Get the access-mode attributes,
161 // but don't set yet (in case they
162 // prevent us from recording the value).
165 attval = atts.getValue("read");
166 if (checkFlag(attval, true))
167 mode |= SGPropertyNode::READ;
168 attval = atts.getValue("write");
169 if (checkFlag(attval, true))
170 mode |= SGPropertyNode::WRITE;
171 attval = atts.getValue("archive");
172 if (checkFlag(attval, false))
173 mode |= SGPropertyNode::ARCHIVE;
175 // Check for an alias.
176 attval = atts.getValue("alias");
178 if (!node->alias(attval))
179 SG_LOG(SG_INPUT, SG_ALERT, "Failed to set alias to " << attval);
182 // Check for an include.
183 attval = atts.getValue("include");
185 SGPath path(SGPath(_base).dir());
186 cerr << "Base is " << _base << endl;
187 cerr << "Dir is " << SGPath(_base).dir() << endl;
189 if (!readProperties(path.str(), node)) {
190 SG_LOG(SG_INPUT, SG_ALERT, "Failed to read include file "
196 push_state(node, atts.getValue("type"), mode);
201 PropsVisitor::endElement (const char * name)
206 // If there are no children and it's
207 // not an alias, then it's a leaf value.
208 if (st.node->nChildren() == 0 && !st.node->isAlias()) {
209 if (st.type == "bool") {
210 if (_data == "true" || atoi(_data.c_str()) != 0)
211 ret = st.node->setBoolValue(true);
213 ret = st.node->setBoolValue(false);
214 } else if (st.type == "int") {
215 ret = st.node->setIntValue(atoi(_data.c_str()));
216 } else if (st.type == "long") {
217 ret = st.node->setLongValue(strtol(_data.c_str(), 0, 0));
218 } else if (st.type == "float") {
219 ret = st.node->setFloatValue(atof(_data.c_str()));
220 } else if (st.type == "double") {
221 ret = st.node->setDoubleValue(strtod(_data.c_str(), 0));
222 } else if (st.type == "string") {
223 ret = st.node->setStringValue(_data);
224 } else if (st.type == "unspecified") {
225 ret = st.node->setUnspecifiedValue(_data);
227 SG_LOG(SG_INPUT, SG_ALERT, "Unrecognized data type " << st.type
228 << " assuming 'unspecified'");
229 ret = st.node->setUnspecifiedValue(_data);
232 SG_LOG(SG_INPUT, SG_ALERT, "readProperties: Failed to set "
233 << st.node->getPath() << " to value \""
234 << _data << "\" with type " << st.type);
237 // Set the access-mode attributes now,
238 // once the value has already been
240 st.node->setAttributes(st.mode);
246 PropsVisitor::data (const char * s, int length)
248 if (state().node->nChildren() == 0)
249 _data.append(string(s, length));
253 PropsVisitor::warning (const char * message, int line, int column)
255 SG_LOG(SG_INPUT, SG_ALERT, "readProperties: warning: "
256 << message << " at line " << line << ", column " << column);
260 PropsVisitor::error (const char * message, int line, int column)
262 SG_LOG(SG_INPUT, SG_ALERT, "readProperties: FATAL: " <<
263 message << " at line " << line << ", column " << column);
269 ////////////////////////////////////////////////////////////////////////
270 // Property list reader.
271 ////////////////////////////////////////////////////////////////////////
275 * Read properties from an input stream.
277 * @param input The input stream containing an XML property file.
278 * @param start_node The root node for reading properties.
279 * @param base A base path for resolving external include references.
280 * @return true if the read succeeded, false otherwise.
283 readProperties (istream &input, SGPropertyNode * start_node,
286 PropsVisitor visitor(start_node, base);
287 return readXML(input, visitor) && visitor.isOK();
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 cerr << "Reading properties from " << file << endl;
302 ifstream input(file.c_str());
304 return readProperties(input, start_node, file);
306 SG_LOG(SG_INPUT, SG_ALERT, "Error reading property list from file "
314 ////////////////////////////////////////////////////////////////////////
315 // Property list writer.
316 ////////////////////////////////////////////////////////////////////////
318 #define INDENT_STEP 2
321 * Return the type name.
324 getTypeName (SGPropertyNode::Type type)
327 case SGPropertyNode::UNSPECIFIED:
328 return "unspecified";
329 case SGPropertyNode::BOOL:
331 case SGPropertyNode::INT:
333 case SGPropertyNode::LONG:
335 case SGPropertyNode::FLOAT:
337 case SGPropertyNode::DOUBLE:
339 case SGPropertyNode::STRING:
343 // keep the compiler from squawking
344 return "unspecified";
349 * Escape characters for output.
352 writeData (ostream &output, const string &data)
354 for (int i = 0; i < (int)data.size(); i++) {
373 doIndent (ostream &output, int indent)
375 while (indent-- > 0) {
382 writeAtts (ostream &output, const SGPropertyNode * node)
384 int index = node->getIndex();
387 output << " n=\"" << index << '"';
390 if (!node->getAttribute(SGPropertyNode::READ))
391 output << " read=\"n\"";
393 if (!node->getAttribute(SGPropertyNode::WRITE))
394 output << " write=\"n\"";
396 if (node->getAttribute(SGPropertyNode::ARCHIVE))
397 output << " archive=\"y\"";
404 * Test whether a node is archivable or has archivable descendants.
407 isArchivable (const SGPropertyNode * node)
409 // FIXME: it's inefficient to do this all the time
410 if (node->getAttribute(SGPropertyNode::ARCHIVE))
413 int nChildren = node->nChildren();
414 for (int i = 0; i < nChildren; i++)
415 if (isArchivable(node->getChild(i)))
423 writeNode (ostream &output, const SGPropertyNode * node, int indent)
425 // Don't write the node or any of
426 // its descendants unless it is
427 // allowed to be archived.
428 if (!isArchivable(node))
429 return true; // Everything's OK, but we won't write.
431 const string &name = node->getName();
432 int index = node->getIndex();
433 int nChildren = node->nChildren();
435 // If there is a literal value,
437 if (node->hasValue() && node->getAttribute(SGPropertyNode::ARCHIVE)) {
438 doIndent(output, indent);
439 output << '<' << name;
440 writeAtts(output, node);
441 if (node->isAlias() && node->getAliasTarget() != 0) {
442 output << " alias=\"" << node->getAliasTarget()->getPath()
445 if (node->getType() != SGPropertyNode::UNSPECIFIED)
446 output << " type=\"" << getTypeName(node->getType()) << '"';
448 writeData(output, node->getStringValue());
449 output << "</" << name << '>' << endl;
453 // If there are children, write them
455 if (nChildren > 0 || node->isAlias()) {
456 doIndent(output, indent);
457 output << '<' << name;
458 writeAtts(output, node);
459 output << '>' << endl;
460 for (int i = 0; i < nChildren; i++)
461 writeNode(output, node->getChild(i), indent + INDENT_STEP);
462 doIndent(output, indent);
463 output << "</" << name << '>' << endl;
471 * Write a property tree to an output stream in XML format.
473 * @param output The output stream.
474 * @param start_node The root node to write.
475 * @return true if the write succeeded, false otherwise.
478 writeProperties (ostream &output, const SGPropertyNode * start_node)
480 int nChildren = start_node->nChildren();
482 output << "<?xml version=\"1.0\"?>" << endl << endl;
483 output << "<PropertyList>" << endl;
485 for (int i = 0; i < nChildren; i++) {
486 writeNode(output, start_node->getChild(i), INDENT_STEP);
489 output << "</PropertyList>" << endl;
496 * Write a property tree to a file in XML format.
498 * @param file The destination file.
499 * @param start_node The root node to write.
500 * @return true if the write succeeded, false otherwise.
503 writeProperties (const string &file, const SGPropertyNode * start_node)
505 ofstream output(file.c_str());
507 return writeProperties(output, start_node);
509 SG_LOG(SG_INPUT, SG_ALERT, "Cannot write properties to file "
517 ////////////////////////////////////////////////////////////////////////
518 // Copy properties from one tree to another.
519 ////////////////////////////////////////////////////////////////////////
523 * Copy one property tree to another.
525 * @param in The source property tree.
526 * @param out The destination property tree.
527 * @return true if all properties were copied, false if some failed
528 * (for example, if the property's value is tied read-only).
531 copyProperties (const SGPropertyNode *in, SGPropertyNode *out)
535 // First, copy the actual value,
537 if (in->hasValue()) {
538 switch (in->getType()) {
539 case SGPropertyNode::BOOL:
540 if (!out->setBoolValue(in->getBoolValue()))
543 case SGPropertyNode::INT:
544 if (!out->setIntValue(in->getIntValue()))
547 case SGPropertyNode::LONG:
548 if (!out->setLongValue(in->getLongValue()))
551 case SGPropertyNode::FLOAT:
552 if (!out->setFloatValue(in->getFloatValue()))
555 case SGPropertyNode::DOUBLE:
556 if (!out->setDoubleValue(in->getDoubleValue()))
559 case SGPropertyNode::STRING:
560 if (!out->setStringValue(in->getStringValue()))
563 case SGPropertyNode::UNSPECIFIED:
564 if (!out->setUnspecifiedValue(in->getStringValue()))
568 throw string("Unrecognized SGPropertyNode type");
572 // Next, copy the children.
573 int nChildren = in->nChildren();
574 for (int i = 0; i < nChildren; i++) {
575 const SGPropertyNode * in_child = in->getChild(i);
576 SGPropertyNode * out_child = out->getChild(in_child->getName(),
577 in_child->getIndex(),
579 if (!copyProperties(in_child, out_child))
586 // end of props_io.cxx