X-Git-Url: https://git.mxchange.org/?a=blobdiff_plain;f=simgear%2Fmisc%2Fprops_io.cxx;h=e4b8e8009e5d762215bdc894f2d8582e944fb2f6;hb=f1a79e6b9fb70208f19bd9c19f68617addcca2e0;hp=35c282f6e73f76cced617ab943cc59df6f5c9557;hpb=502c650cd9b25ba013f77194c3c13274504aa090;p=simgear.git diff --git a/simgear/misc/props_io.cxx b/simgear/misc/props_io.cxx index 35c282f6..e4b8e800 100644 --- a/simgear/misc/props_io.cxx +++ b/simgear/misc/props_io.cxx @@ -1,212 +1,266 @@ -#ifdef HAVE_CONFIG_H -# include -#endif +#include + +#include // atof() atoi() +#include #include #include +#include "sg_path.hxx" #include "props.hxx" -#include -#include -#include +#include STL_IOSTREAM +#if !defined(SG_HAVE_NATIVE_SGI_COMPILERS) +# include +#else +# include +#endif +#include STL_STRING #include +#include + +#if !defined(SG_HAVE_NATIVE_SGI_COMPILERS) +SG_USING_STD(istream); +SG_USING_STD(ifstream); +SG_USING_STD(ostream); +SG_USING_STD(ofstream); +#endif +SG_USING_STD(string); +SG_USING_STD(vector); +SG_USING_STD(map); + +#define DEFAULT_MODE (SGPropertyNode::READ|SGPropertyNode::WRITE) -using std::istream; -using std::ifstream; -using std::ostream; -using std::string; -using std::vector; //////////////////////////////////////////////////////////////////////// -// Visitor class for building the property list. +// Property list visitor, for XML parsing. //////////////////////////////////////////////////////////////////////// -class PropVisitor : public XMLVisitor +class PropsVisitor : public XMLVisitor { public: - PropVisitor (SGPropertyList * props) : _props(props), _level(0), _ok(true) {} - void startDocument (); + + PropsVisitor (SGPropertyNode * root, const string &base) + : _ok(true), _root(root), _level(0), _base(base) {} + + void startXML (); + void endXML (); void startElement (const char * name, const XMLAttributes &atts); void endElement (const char * name); void data (const char * s, int length); - void warning (const char * message, int line, int col); - void error (const char * message, int line, int col); + void warning (const char * message, int line, int column); + void error (const char * message, int line, int column); bool isOK () const { return _ok; } private: - void pushState (const char * name) { - _states.push_back(_state); + struct State + { + State () : node(0), type(""), mode(DEFAULT_MODE) {} + State (SGPropertyNode * _node, const char * _type, int _mode) + : node(_node), type(_type), mode(_mode) {} + SGPropertyNode * node; + string type; + int mode; + map counters; + }; + + State &state () { return _state_stack[_state_stack.size() - 1]; } + + void push_state (SGPropertyNode * node, const char * type, int mode) { + if (type == 0) + _state_stack.push_back(State(node, "unspecified", mode)); + else + _state_stack.push_back(State(node, type, mode)); _level++; - _state.name = name; - _state.type = SGValue::UNKNOWN; - _state.data = ""; - _state.hasChildren = false; - _state.hasData = false; + _data = ""; } - void popState () { - _state = _states.back(); - _states.pop_back(); + void pop_state () { + _state_stack.pop_back(); _level--; } - struct State - { - State () : hasChildren(false), hasData(false) {} - string name; - SGValue::Type type; - string data; - bool hasChildren; - bool hasData; - }; - - SGPropertyList * _props; - State _state; - vector _states; - int _level; bool _ok; + string _data; + SGPropertyNode * _root; + int _level; + vector _state_stack; + string _base; }; void -PropVisitor::startDocument () +PropsVisitor::startXML () { _level = 0; - _ok = true; + _state_stack.resize(0); } - void -PropVisitor::startElement (const char * name, const XMLAttributes &atts) +PropsVisitor::endXML () { - if (!_ok) - return; - - if (_level == 0 && strcmp(name, "PropertyList")) { - _ok = false; - FG_LOG(FG_INPUT, FG_ALERT, "XML document has root element \"" - << name << "\" instead of \"PropertyList\""); - return; - } + _level = 0; + _state_stack.resize(0); +} - // Mixed content? - _state.hasChildren = true; - if (_state.hasData) { - FG_LOG(FG_INPUT, FG_ALERT, - "XML element has mixed elements and data in element " - << _state.name); - _ok = false; - return; - } - // Start a new state. - pushState(name); - - // See if there's a type specified. - const char * type = atts.getValue("type"); - if (type == 0 || !strcmp("unknown", type)) - _state.type = SGValue::UNKNOWN; - else if (!strcmp("bool", type)) - _state.type = SGValue::BOOL; - else if (!strcmp("int", type)) - _state.type = SGValue::INT; - else if (!strcmp("float", type)) - _state.type = SGValue::FLOAT; - else if (!strcmp("double", type)) - _state.type = SGValue::DOUBLE; - else if (!strcmp("string", type)) - _state.type = SGValue::STRING; - else - FG_LOG(FG_INPUT, FG_ALERT, "Unrecognized type " << type - << ", using UNKNOWN"); +/** + * Check a yes/no flag, with default. + */ +static bool +checkFlag (const char * flag, bool defaultState = true) +{ + if (flag == 0) + return defaultState; + else if (string(flag) == "y") + return true; + else if (string(flag) == "n") + return false; + else { + SG_LOG(SG_INPUT, SG_ALERT, "Unrecognized flag value '" << flag + << "', assuming yes"); + return true; + } } void -PropVisitor::endElement (const char * name) +PropsVisitor::startElement (const char * name, const XMLAttributes &atts) { - if (!_ok) - return; - - // See if there's a property to add. - if (_state.hasData) { - bool status = false; - - // Figure out the path name. - string path = ""; - for (int i = 2; i < _level; i++) { - path += '/'; - path += _states[i].name; + State &st = state(); + + if (_level == 0) { + if (string(name) != (string)"PropertyList") { + SG_LOG(SG_INPUT, SG_ALERT, "Root element name is " << + name << "; expected PropertyList"); + _ok = false; + } + push_state(_root, "", DEFAULT_MODE); + } + + else { + + const char * attval; + // Get the index. + attval = atts.getValue("n"); + int index = 0; + if (attval != 0) { + index = atoi(attval); + st.counters[name] = SG_MAX2(st.counters[name], index+1); + } else { + index = st.counters[name]; + st.counters[name]++; + } + + // Got the index, so grab the node. + SGPropertyNode * node = st.node->getChild(name, index, true); + + // Get the access-mode attributes, + // but don't set yet (in case they + // prevent us from recording the value). + int mode = 0; + + attval = atts.getValue("read"); + if (checkFlag(attval, true)) + mode |= SGPropertyNode::READ; + attval = atts.getValue("write"); + if (checkFlag(attval, true)) + mode |= SGPropertyNode::WRITE; + attval = atts.getValue("archive"); + if (checkFlag(attval, false)) + mode |= SGPropertyNode::ARCHIVE; + + // Check for an alias. + attval = atts.getValue("alias"); + if (attval != 0) { + if (!node->alias(attval)) + SG_LOG(SG_INPUT, SG_ALERT, "Failed to set alias to " << attval); } - path += '/'; - path += _state.name; - - // Set the value - switch (_state.type) { - case SGValue::BOOL: - if (_state.data == "true" || _state.data == "TRUE") { - status = _props->setBoolValue(path, true); - } else if (atof(_state.data.c_str()) != 0.0) { - status = _props->setBoolValue(path, true); - } else { - status =_props->setBoolValue(path, false); + + // Check for an include. + attval = atts.getValue("include"); + if (attval != 0) { + SGPath path(SGPath(_base).dir()); + cerr << "Base is " << _base << endl; + cerr << "Dir is " << SGPath(_base).dir() << endl; + path.append(attval); + if (!readProperties(path.str(), node)) { + SG_LOG(SG_INPUT, SG_ALERT, "Failed to read include file " + << attval); + _ok = false; } - break; - case SGValue::INT : - status = _props->setIntValue(path, atoi(_state.data.c_str())); - break; - case SGValue::FLOAT: - status = _props->setFloatValue(path, atof(_state.data.c_str())); - break; - case SGValue::DOUBLE: - status = _props->setDoubleValue(path, atof(_state.data.c_str())); - break; - case SGValue::STRING: - status = _props->setStringValue(path, _state.data); - break; - default: - status = _props->setUnknownValue(path, _state.data); - break; } - if (!status) - FG_LOG(FG_INPUT, FG_ALERT, "Failed to set property " - << path << " to " << _state.data); - } - // Pop the stack. - popState(); + push_state(node, atts.getValue("type"), mode); + } } void -PropVisitor::data (const char * s, int length) +PropsVisitor::endElement (const char * name) { - if (!_ok) - return; + State &st = state(); + bool ret; + + // If there are no children and it's + // not an alias, then it's a leaf value. + if (st.node->nChildren() == 0 && !st.node->isAlias()) { + if (st.type == "bool") { + if (_data == "true" || atoi(_data.c_str()) != 0) + ret = st.node->setBoolValue(true); + else + ret = st.node->setBoolValue(false); + } else if (st.type == "int") { + ret = st.node->setIntValue(atoi(_data.c_str())); + } else if (st.type == "long") { + ret = st.node->setLongValue(strtol(_data.c_str(), 0, 0)); + } else if (st.type == "float") { + ret = st.node->setFloatValue(atof(_data.c_str())); + } else if (st.type == "double") { + ret = st.node->setDoubleValue(strtod(_data.c_str(), 0)); + } else if (st.type == "string") { + ret = st.node->setStringValue(_data); + } else if (st.type == "unspecified") { + ret = st.node->setUnspecifiedValue(_data); + } else { + SG_LOG(SG_INPUT, SG_ALERT, "Unrecognized data type " << st.type + << " assuming 'unspecified'"); + ret = st.node->setUnspecifiedValue(_data); + } + if (!ret) + SG_LOG(SG_INPUT, SG_ALERT, "readProperties: Failed to set " + << st.node->getPath() << " to value \"" + << _data << "\" with type " << st.type); + } - // Check if there is any non-whitespace - if (!_state.hasData) - for (int i = 0; i < length; i++) - if (s[i] != ' ' && s[i] != '\t' && s[i] != '\n' && s[i] != '\r') - _state.hasData = true; + // Set the access-mode attributes now, + // once the value has already been + // assigned. + st.node->setAttributes(st.mode); - _state.data += string(s, length); // FIXME: inefficient + pop_state(); +} + +void +PropsVisitor::data (const char * s, int length) +{ + if (state().node->nChildren() == 0) + _data.append(string(s, length)); } void -PropVisitor::warning (const char * message, int line, int col) +PropsVisitor::warning (const char * message, int line, int column) { - FG_LOG(FG_INPUT, FG_ALERT, "Warning importing property list: " - << message << " (" << line << ',' << col << ')'); + SG_LOG(SG_INPUT, SG_ALERT, "readProperties: warning: " + << message << " at line " << line << ", column " << column); } void -PropVisitor::error (const char * message, int line, int col) +PropsVisitor::error (const char * message, int line, int column) { - FG_LOG(FG_INPUT, FG_ALERT, "Error importing property list: " - << message << " (" << line << ',' << col << ')'); + SG_LOG(SG_INPUT, SG_ALERT, "readProperties: FATAL: " << + message << " at line " << line << ", column " << column); _ok = false; } @@ -216,21 +270,40 @@ PropVisitor::error (const char * message, int line, int col) // Property list reader. //////////////////////////////////////////////////////////////////////// + +/** + * Read properties from an input stream. + * + * @param input The input stream containing an XML property file. + * @param start_node The root node for reading properties. + * @param base A base path for resolving external include references. + * @return true if the read succeeded, false otherwise. + */ bool -readPropertyList (istream &input, SGPropertyList * props) +readProperties (istream &input, SGPropertyNode * start_node, + const string &base) { - PropVisitor visitor(props); + PropsVisitor visitor(start_node, base); return readXML(input, visitor) && visitor.isOK(); } + +/** + * Read properties from a file. + * + * @param file A string containing the file path. + * @param start_node The root node for reading properties. + * @return true if the read succeeded, false otherwise. + */ bool -readPropertyList (const string &file, SGPropertyList * props) +readProperties (const string &file, SGPropertyNode * start_node) { + cerr << "Reading properties from " << file << endl; ifstream input(file.c_str()); if (input.good()) { - return readPropertyList(input, props); + return readProperties(input, start_node, file); } else { - FG_LOG(FG_INPUT, FG_ALERT, "Error reading property list from file " + SG_LOG(SG_INPUT, SG_ALERT, "Error reading property list from file " << file); return false; } @@ -248,22 +321,27 @@ readPropertyList (const string &file, SGPropertyList * props) * Return the type name. */ static const char * -getTypeName (SGValue::Type type) +getTypeName (SGPropertyNode::Type type) { switch (type) { - case SGValue::UNKNOWN: - return "unknown"; - case SGValue::BOOL: + case SGPropertyNode::UNSPECIFIED: + return "unspecified"; + case SGPropertyNode::BOOL: return "bool"; - case SGValue::INT: + case SGPropertyNode::INT: return "int"; - case SGValue::FLOAT: + case SGPropertyNode::LONG: + return "long"; + case SGPropertyNode::FLOAT: return "float"; - case SGValue::DOUBLE: + case SGPropertyNode::DOUBLE: return "double"; - case SGValue::STRING: + case SGPropertyNode::STRING: return "string"; } + + // keep the compiler from squawking + return "unspecified"; } @@ -273,7 +351,7 @@ getTypeName (SGValue::Type type) static void writeData (ostream &output, const string &data) { - for (int i = 0; i < data.size(); i++) { + for (int i = 0; i < (int)data.size(); i++) { switch (data[i]) { case '&': output << "&"; @@ -300,61 +378,209 @@ doIndent (ostream &output, int indent) } +static void +writeAtts (ostream &output, const SGPropertyNode * node) +{ + int index = node->getIndex(); + + if (index != 0) + output << " n=\"" << index << '"'; + +#if 0 + if (!node->getAttribute(SGPropertyNode::READ)) + output << " read=\"n\""; + + if (!node->getAttribute(SGPropertyNode::WRITE)) + output << " write=\"n\""; + + if (node->getAttribute(SGPropertyNode::ARCHIVE)) + output << " archive=\"y\""; +#endif + +} + + +/** + * Test whether a node is archivable or has archivable descendants. + */ static bool -writeNode (ostream &output, SGPropertyNode node, int indent) +isArchivable (const SGPropertyNode * node) { - const string &name = node.getName(); - int size = node.size(); + // FIXME: it's inefficient to do this all the time + if (node->getAttribute(SGPropertyNode::ARCHIVE)) + return true; + else { + int nChildren = node->nChildren(); + for (int i = 0; i < nChildren; i++) + if (isArchivable(node->getChild(i))) + return true; + } + return false; +} + - // Write out the literal value, if any. - SGValue * value = node.getValue(); - if (value != 0) { - SGValue::Type type = value->getType(); +static bool +writeNode (ostream &output, const SGPropertyNode * node, int indent) +{ + // Don't write the node or any of + // its descendants unless it is + // allowed to be archived. + if (!isArchivable(node)) + return true; // Everything's OK, but we won't write. + + const string &name = node->getName(); + int index = node->getIndex(); + int nChildren = node->nChildren(); + + // If there is a literal value, + // write it first. + if (node->hasValue() && node->getAttribute(SGPropertyNode::ARCHIVE)) { doIndent(output, indent); output << '<' << name; - if (type != SGValue::UNKNOWN) - output << " type=\"" << getTypeName(type) << '"'; - output << '>'; - writeData(output, value->getStringValue()); - output << "' << endl; + writeAtts(output, node); + if (node->isAlias() && node->getAliasTarget() != 0) { + output << " alias=\"" << node->getAliasTarget()->getPath() + << "\"/>" << endl; + } else { + if (node->getType() != SGPropertyNode::UNSPECIFIED) + output << " type=\"" << getTypeName(node->getType()) << '"'; + output << '>'; + writeData(output, node->getStringValue()); + output << "' << endl; + } } - // Write out the children, if any. - if (size > 0) { + // If there are children, write them + // next. + if (nChildren > 0 || node->isAlias()) { doIndent(output, indent); - output << '<' << name << '>' << endl;; - for (int i = 0; i < size; i++) { - writeNode(output, node.getChild(i), indent + INDENT_STEP); - } + output << '<' << name; + writeAtts(output, node); + output << '>' << endl; + for (int i = 0; i < nChildren; i++) + writeNode(output, node->getChild(i), indent + INDENT_STEP); doIndent(output, indent); output << "' << endl; } + + return true; } + +/** + * Write a property tree to an output stream in XML format. + * + * @param output The output stream. + * @param start_node The root node to write. + * @return true if the write succeeded, false otherwise. + */ bool -writePropertyList (ostream &output, const SGPropertyList * props) +writeProperties (ostream &output, const SGPropertyNode * start_node) { - SGPropertyNode root ("/", (SGPropertyList *)props); // FIXME + int nChildren = start_node->nChildren(); output << "" << endl << endl; output << "" << endl; - for (int i = 0; i < root.size(); i++) { - writeNode(output, root.getChild(i), INDENT_STEP); + for (int i = 0; i < nChildren; i++) { + writeNode(output, start_node->getChild(i), INDENT_STEP); } output << "" << endl; + + return true; } + +/** + * Write a property tree to a file in XML format. + * + * @param file The destination file. + * @param start_node The root node to write. + * @return true if the write succeeded, false otherwise. + */ bool -writePropertyList (const string &file, const SGPropertyList * props) +writeProperties (const string &file, const SGPropertyNode * start_node) { ofstream output(file.c_str()); if (output.good()) { - return writePropertyList(output, props); + return writeProperties(output, start_node); } else { - FG_LOG(FG_INPUT, FG_ALERT, "Cannot write property list to file " + SG_LOG(SG_INPUT, SG_ALERT, "Cannot write properties to file " << file); return false; } } + + + +//////////////////////////////////////////////////////////////////////// +// Copy properties from one tree to another. +//////////////////////////////////////////////////////////////////////// + + +/** + * Copy one property tree to another. + * + * @param in The source property tree. + * @param out The destination property tree. + * @return true if all properties were copied, false if some failed + * (for example, if the property's value is tied read-only). + */ +bool +copyProperties (const SGPropertyNode *in, SGPropertyNode *out) +{ + bool retval = true; + + // First, copy the actual value, + // if any. + if (in->hasValue()) { + switch (in->getType()) { + case SGPropertyNode::BOOL: + if (!out->setBoolValue(in->getBoolValue())) + retval = false; + break; + case SGPropertyNode::INT: + if (!out->setIntValue(in->getIntValue())) + retval = false; + break; + case SGPropertyNode::LONG: + if (!out->setLongValue(in->getLongValue())) + retval = false; + break; + case SGPropertyNode::FLOAT: + if (!out->setFloatValue(in->getFloatValue())) + retval = false; + break; + case SGPropertyNode::DOUBLE: + if (!out->setDoubleValue(in->getDoubleValue())) + retval = false; + break; + case SGPropertyNode::STRING: + if (!out->setStringValue(in->getStringValue())) + retval = false; + break; + case SGPropertyNode::UNSPECIFIED: + if (!out->setUnspecifiedValue(in->getStringValue())) + retval = false; + break; + default: + throw string("Unrecognized SGPropertyNode type"); + } + } + + // Next, copy the children. + int nChildren = in->nChildren(); + for (int i = 0; i < nChildren; i++) { + const SGPropertyNode * in_child = in->getChild(i); + SGPropertyNode * out_child = out->getChild(in_child->getName(), + in_child->getIndex(), + true); + if (!copyProperties(in_child, out_child)) + retval = false; + } + + return retval; +} + +// end of props_io.cxx