X-Git-Url: https://git.mxchange.org/?a=blobdiff_plain;f=simgear%2Fmisc%2Fprops_io.cxx;h=160539ae73b41309c9f6d1fe88b9bdeada65976c;hb=5bab565cfe4c30d6cf08ecaba50af74d5e4f0c98;hp=ebe0f0f67c3ed9f1a3b2f02565cf0ae479e3139a;hpb=e175c8cc4d957e8f733a0aa61da850a7c2e858ae;p=simgear.git diff --git a/simgear/misc/props_io.cxx b/simgear/misc/props_io.cxx index ebe0f0f6..160539ae 100644 --- a/simgear/misc/props_io.cxx +++ b/simgear/misc/props_io.cxx @@ -1,214 +1,275 @@ -#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::ofstream; -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) + : _root(root), _level(0), _base(base), _hasException(false) {} + + virtual ~PropsVisitor () {} + + 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); - bool isOK () const { return _ok; } + bool hasException () const { return _hasException; } + sg_io_exception &getException () { return _exception; } + void setException (const sg_io_exception &exception) { + _exception = exception; + _hasException = true; + } 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; + string _data; + SGPropertyNode * _root; int _level; - bool _ok; + vector _state_stack; + string _base; + sg_io_exception _exception; + bool _hasException; }; 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 { + string message = "Unrecognized flag value '"; + message += flag; + message += '\''; + // FIXME: add location info + throw sg_io_exception(message, "SimGear Property Reader"); + } } 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") { + string message = "Root element name is "; + message += name; + message += "; expected PropertyList"; + throw sg_io_exception(message, "SimGear Property Reader"); } - 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); + 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; + attval = atts.getValue("trace-read"); + if (checkFlag(attval, false)) + mode |= SGPropertyNode::TRACE_READ; + attval = atts.getValue("trace-write"); + if (checkFlag(attval, false)) + mode |= SGPropertyNode::TRACE_WRITE; + + // 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); + } + + // Check for an include. + attval = atts.getValue("include"); + if (attval != 0) { + SGPath path(SGPath(_base).dir()); + path.append(attval); + try { + readProperties(path.str(), node); + } catch (sg_io_exception &e) { + setException(e); } - 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 { + string message = "Unrecognized data type '"; + message += st.type; + message += '\''; + // FIXME: add location information + throw sg_io_exception(message, "SimGear Property Reader"); + } + 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 -PropVisitor::warning (const char * message, int line, int col) +PropsVisitor::data (const char * s, int length) { - FG_LOG(FG_INPUT, FG_ALERT, "Warning importing property list: " - << message << " (" << line << ',' << col << ')'); + if (state().node->nChildren() == 0) + _data.append(string(s, length)); } void -PropVisitor::error (const char * message, int line, int col) +PropsVisitor::warning (const char * message, int line, int column) { - FG_LOG(FG_INPUT, FG_ALERT, "Error importing property list: " - << message << " (" << line << ',' << col << ')'); - _ok = false; + SG_LOG(SG_INPUT, SG_ALERT, "readProperties: warning: " + << message << " at line " << line << ", column " << column); } @@ -217,24 +278,40 @@ PropVisitor::error (const char * message, int line, int col) // Property list reader. //////////////////////////////////////////////////////////////////////// -bool -readPropertyList (istream &input, SGPropertyList * props) + +/** + * 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. + */ +void +readProperties (istream &input, SGPropertyNode * start_node, + const string &base) { - PropVisitor visitor(props); - return readXML(input, visitor) && visitor.isOK(); + PropsVisitor visitor(start_node, base); + readXML(input, visitor, base); + if (visitor.hasException()) + throw visitor.getException(); } -bool -readPropertyList (const string &file, SGPropertyList * props) + +/** + * 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. + */ +void +readProperties (const string &file, SGPropertyNode * start_node) { - ifstream input(file.c_str()); - if (input.good()) { - return readPropertyList(input, props); - } else { - FG_LOG(FG_INPUT, FG_ALERT, "Error reading property list from file " - << file); - return false; - } + PropsVisitor visitor(start_node, file); + readXML(file, visitor); + if (visitor.hasException()) + throw visitor.getException(); } @@ -249,22 +326,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"; } @@ -274,7 +356,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 << "&"; @@ -301,32 +383,87 @@ 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; } @@ -334,32 +471,105 @@ writeNode (ostream &output, SGPropertyNode node, int indent) return true; } -bool -writePropertyList (ostream &output, const SGPropertyList * props) + +void +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; } -bool -writePropertyList (const string &file, const SGPropertyList * props) + +void +writeProperties (const string &file, const SGPropertyNode * start_node) { ofstream output(file.c_str()); if (output.good()) { - return writePropertyList(output, props); + writeProperties(output, start_node); } else { - FG_LOG(FG_INPUT, FG_ALERT, "Cannot write property list to file " - << file); - return false; + throw sg_io_exception("Cannot open file", sg_location(file)); } } + + + +//////////////////////////////////////////////////////////////////////// +// 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: + string message = "Unknown internal SGPropertyNode type"; + message += in->getType(); + throw sg_error(message, "SimGear Property Reader"); + } + } + + // 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