3 * Started Fall 2000 by David Megginson, david@megginson.com
4 * This code is released into the Public Domain.
6 * See props.html for documentation [replace with URL when available].
12 # include <simgear_config.h>
15 #include <simgear/compiler.h>
17 #include <stdlib.h> // atof() atoi()
19 #include <simgear/sg_inlines.h>
20 #include <simgear/debug/logstream.hxx>
21 #include <simgear/misc/sg_path.hxx>
22 #include <simgear/xml/easyxml.hxx>
25 #include "props_io.hxx"
33 SG_USING_STD(istream);
34 SG_USING_STD(ifstream);
35 SG_USING_STD(ostream);
36 SG_USING_STD(ofstream);
43 #define DEFAULT_MODE (SGPropertyNode::READ|SGPropertyNode::WRITE)
47 ////////////////////////////////////////////////////////////////////////
48 // Property list visitor, for XML parsing.
49 ////////////////////////////////////////////////////////////////////////
51 class PropsVisitor : public XMLVisitor
55 PropsVisitor (SGPropertyNode * root, const string &base, int default_mode = 0)
56 : _default_mode(default_mode), _root(root), _level(0), _base(base), _hasException(false) {}
58 virtual ~PropsVisitor () {}
62 void startElement (const char * name, const XMLAttributes &atts);
63 void endElement (const char * name);
64 void data (const char * s, int length);
65 void warning (const char * message, int line, int column);
67 bool hasException () const { return _hasException; }
68 sg_io_exception &getException () { return _exception; }
69 void setException (const sg_io_exception &exception) {
70 _exception = exception;
78 State () : node(0), type(""), mode(DEFAULT_MODE) {}
79 State (SGPropertyNode * _node, const char * _type, int _mode)
80 : node(_node), type(_type), mode(_mode) {}
81 SGPropertyNode * node;
84 map<string,int> counters;
87 State &state () { return _state_stack[_state_stack.size() - 1]; }
89 void push_state (SGPropertyNode * node, const char * type, int mode) {
91 _state_stack.push_back(State(node, "unspecified", mode));
93 _state_stack.push_back(State(node, type, mode));
99 _state_stack.pop_back();
105 SGPropertyNode * _root;
107 vector<State> _state_stack;
109 sg_io_exception _exception;
114 PropsVisitor::startXML ()
117 _state_stack.resize(0);
121 PropsVisitor::endXML ()
124 _state_stack.resize(0);
129 * Check a yes/no flag, with default.
132 checkFlag (const char * flag, bool defaultState = true)
136 else if (!strcmp(flag, "y"))
138 else if (!strcmp(flag, "n"))
141 string message = "Unrecognized flag value '";
144 // FIXME: add location info
145 throw sg_io_exception(message, "SimGear Property Reader");
150 PropsVisitor::startElement (const char * name, const XMLAttributes &atts)
155 if (strcmp(name, "PropertyList")) {
156 string message = "Root element name is ";
158 message += "; expected PropertyList";
159 throw sg_io_exception(message, "SimGear Property Reader");
162 // Check for an include.
163 attval = atts.getValue("include");
165 SGPath path(SGPath(_base).dir());
168 readProperties(path.str(), _root);
169 } catch (sg_io_exception &e) {
174 push_state(_root, "", DEFAULT_MODE);
180 attval = atts.getValue("n");
183 index = atoi(attval);
184 st.counters[name] = SG_MAX2(st.counters[name], index+1);
186 index = st.counters[name];
190 // Got the index, so grab the node.
191 SGPropertyNode * node = st.node->getChild(name, index, true);
193 // Get the access-mode attributes,
194 // but don't set yet (in case they
195 // prevent us from recording the value).
196 int mode = _default_mode;
198 attval = atts.getValue("read");
199 if (checkFlag(attval, true))
200 mode |= SGPropertyNode::READ;
201 attval = atts.getValue("write");
202 if (checkFlag(attval, true))
203 mode |= SGPropertyNode::WRITE;
204 attval = atts.getValue("archive");
205 if (checkFlag(attval, false))
206 mode |= SGPropertyNode::ARCHIVE;
207 attval = atts.getValue("trace-read");
208 if (checkFlag(attval, false))
209 mode |= SGPropertyNode::TRACE_READ;
210 attval = atts.getValue("trace-write");
211 if (checkFlag(attval, false))
212 mode |= SGPropertyNode::TRACE_WRITE;
213 attval = atts.getValue("userarchive");
214 if (checkFlag(attval, false))
215 mode |= SGPropertyNode::USERARCHIVE;
217 // Check for an alias.
218 attval = atts.getValue("alias");
220 if (!node->alias(attval))
221 SG_LOG(SG_INPUT, SG_ALERT, "Failed to set alias to " << attval);
224 // Check for an include.
225 attval = atts.getValue("include");
227 SGPath path(SGPath(_base).dir());
230 readProperties(path.str(), node);
231 } catch (sg_io_exception &e) {
235 const char *omit = atts.getValue("omit-node");
236 if (omit && !strcmp(omit, "y")) {
237 int nChildren = node->nChildren();
238 for (int i = 0; i < nChildren; i++) {
239 SGPropertyNode *src = node->getChild(i);
240 const char *name = src->getName();
241 int index = st.counters[name];
243 SGPropertyNode *dst = st.node->getChild(name, index, true);
244 copyProperties(src, dst);
246 st.node->removeChild(node->getName(), node->getIndex(), false);
251 const char *type = atts.getValue("type");
254 push_state(node, type, mode);
259 PropsVisitor::endElement (const char * name)
264 // If there are no children and it's
265 // not an alias, then it's a leaf value.
266 if (st.node->nChildren() == 0 && !st.node->isAlias()) {
267 if (st.type == "bool") {
268 if (_data == "true" || atoi(_data.c_str()) != 0)
269 ret = st.node->setBoolValue(true);
271 ret = st.node->setBoolValue(false);
272 } else if (st.type == "int") {
273 ret = st.node->setIntValue(atoi(_data.c_str()));
274 } else if (st.type == "long") {
275 ret = st.node->setLongValue(strtol(_data.c_str(), 0, 0));
276 } else if (st.type == "float") {
277 ret = st.node->setFloatValue(atof(_data.c_str()));
278 } else if (st.type == "double") {
279 ret = st.node->setDoubleValue(strtod(_data.c_str(), 0));
280 } else if (st.type == "string") {
281 ret = st.node->setStringValue(_data.c_str());
282 } else if (st.type == "unspecified") {
283 ret = st.node->setUnspecifiedValue(_data.c_str());
284 } else if (_level == 1) {
285 ret = true; // empty <PropertyList>
287 string message = "Unrecognized data type '";
290 // FIXME: add location information
291 throw sg_io_exception(message, "SimGear Property Reader");
294 SG_LOG(SG_INPUT, SG_ALERT, "readProperties: Failed to set "
295 << st.node->getPath() << " to value \""
296 << _data << "\" with type " << st.type);
299 // Set the access-mode attributes now,
300 // once the value has already been
302 st.node->setAttributes(st.mode);
308 PropsVisitor::data (const char * s, int length)
310 if (state().node->nChildren() == 0)
311 _data.append(string(s, length));
315 PropsVisitor::warning (const char * message, int line, int column)
317 SG_LOG(SG_INPUT, SG_ALERT, "readProperties: warning: "
318 << message << " at line " << line << ", column " << column);
323 ////////////////////////////////////////////////////////////////////////
324 // Property list reader.
325 ////////////////////////////////////////////////////////////////////////
329 * Read properties from an input stream.
331 * @param input The input stream containing an XML property file.
332 * @param start_node The root node for reading properties.
333 * @param base A base path for resolving external include references.
334 * @return true if the read succeeded, false otherwise.
337 readProperties (istream &input, SGPropertyNode * start_node,
338 const string &base, int default_mode)
340 PropsVisitor visitor(start_node, base, default_mode);
341 readXML(input, visitor, base);
342 if (visitor.hasException())
343 throw visitor.getException();
348 * Read properties from a file.
350 * @param file A string containing the file path.
351 * @param start_node The root node for reading properties.
352 * @return true if the read succeeded, false otherwise.
355 readProperties (const string &file, SGPropertyNode * start_node,
358 PropsVisitor visitor(start_node, file, default_mode);
359 readXML(file, visitor);
360 if (visitor.hasException())
361 throw visitor.getException();
366 * Read properties from an in-memory buffer.
368 * @param buf A character buffer containing the xml data.
369 * @param size The size/length of the buffer in bytes
370 * @param start_node The root node for reading properties.
371 * @return true if the read succeeded, false otherwise.
373 void readProperties (const char *buf, const int size,
374 SGPropertyNode * start_node, int default_mode)
376 PropsVisitor visitor(start_node, "", default_mode);
377 readXML(buf, size, visitor);
378 if (visitor.hasException())
379 throw visitor.getException();
383 ////////////////////////////////////////////////////////////////////////
384 // Property list writer.
385 ////////////////////////////////////////////////////////////////////////
387 #define INDENT_STEP 2
390 * Return the type name.
393 getTypeName (SGPropertyNode::Type type)
396 case SGPropertyNode::UNSPECIFIED:
397 return "unspecified";
398 case SGPropertyNode::BOOL:
400 case SGPropertyNode::INT:
402 case SGPropertyNode::LONG:
404 case SGPropertyNode::FLOAT:
406 case SGPropertyNode::DOUBLE:
408 case SGPropertyNode::STRING:
410 case SGPropertyNode::ALIAS:
411 case SGPropertyNode::NONE:
412 return "unspecified";
415 // keep the compiler from squawking
416 return "unspecified";
421 * Escape characters for output.
424 writeData (ostream &output, const string &data)
426 for (int i = 0; i < (int)data.size(); i++) {
445 doIndent (ostream &output, int indent)
447 while (indent-- > 0) {
454 writeAtts (ostream &output, const SGPropertyNode * node, bool forceindex)
456 int index = node->getIndex();
458 if (index != 0 || forceindex)
459 output << " n=\"" << index << '"';
462 if (!node->getAttribute(SGPropertyNode::READ))
463 output << " read=\"n\"";
465 if (!node->getAttribute(SGPropertyNode::WRITE))
466 output << " write=\"n\"";
468 if (node->getAttribute(SGPropertyNode::ARCHIVE))
469 output << " archive=\"y\"";
476 * Test whether a node is archivable or has archivable descendants.
479 isArchivable (const SGPropertyNode * node, SGPropertyNode::Attribute archive_flag)
481 // FIXME: it's inefficient to do this all the time
482 if (node->getAttribute(archive_flag))
485 int nChildren = node->nChildren();
486 for (int i = 0; i < nChildren; i++)
487 if (isArchivable(node->getChild(i), archive_flag))
495 writeNode (ostream &output, const SGPropertyNode * node,
496 bool write_all, int indent, SGPropertyNode::Attribute archive_flag)
498 // Don't write the node or any of
499 // its descendants unless it is
500 // allowed to be archived.
501 if (!write_all && !isArchivable(node, archive_flag))
502 return true; // Everything's OK, but we won't write.
504 const string name = node->getName();
505 int nChildren = node->nChildren();
506 bool node_has_value = false;
508 // If there is a literal value,
510 if (node->hasValue() && (write_all || node->getAttribute(archive_flag))) {
511 doIndent(output, indent);
512 output << '<' << name;
513 writeAtts(output, node, nChildren != 0);
514 if (node->isAlias() && node->getAliasTarget() != 0) {
515 output << " alias=\"" << node->getAliasTarget()->getPath()
518 if (node->getType() != SGPropertyNode::UNSPECIFIED)
519 output << " type=\"" << getTypeName(node->getType()) << '"';
521 writeData(output, node->getStringValue());
522 output << "</" << name << '>' << endl;
524 node_has_value = true;
527 // If there are children, write them next.
529 doIndent(output, indent);
530 output << '<' << name;
531 writeAtts(output, node, node_has_value);
532 output << '>' << endl;
533 for (int i = 0; i < nChildren; i++)
534 writeNode(output, node->getChild(i), write_all, indent + INDENT_STEP, archive_flag);
535 doIndent(output, indent);
536 output << "</" << name << '>' << endl;
544 writeProperties (ostream &output, const SGPropertyNode * start_node,
545 bool write_all, SGPropertyNode::Attribute archive_flag)
547 int nChildren = start_node->nChildren();
549 output << "<?xml version=\"1.0\"?>" << endl << endl;
550 output << "<PropertyList>" << endl;
552 for (int i = 0; i < nChildren; i++) {
553 writeNode(output, start_node->getChild(i), write_all, INDENT_STEP, archive_flag);
556 output << "</PropertyList>" << endl;
561 writeProperties (const string &file, const SGPropertyNode * start_node,
562 bool write_all, SGPropertyNode::Attribute archive_flag)
564 SGPath path(file.c_str());
565 path.create_dir(0777);
567 ofstream output(file.c_str());
569 writeProperties(output, start_node, write_all, archive_flag);
571 throw sg_io_exception("Cannot open file", sg_location(file));
577 ////////////////////////////////////////////////////////////////////////
578 // Copy properties from one tree to another.
579 ////////////////////////////////////////////////////////////////////////
583 * Copy one property tree to another.
585 * @param in The source property tree.
586 * @param out The destination property tree.
587 * @return true if all properties were copied, false if some failed
588 * (for example, if the property's value is tied read-only).
591 copyProperties (const SGPropertyNode *in, SGPropertyNode *out)
595 // First, copy the actual value,
597 if (in->hasValue()) {
598 switch (in->getType()) {
599 case SGPropertyNode::BOOL:
600 if (!out->setBoolValue(in->getBoolValue()))
603 case SGPropertyNode::INT:
604 if (!out->setIntValue(in->getIntValue()))
607 case SGPropertyNode::LONG:
608 if (!out->setLongValue(in->getLongValue()))
611 case SGPropertyNode::FLOAT:
612 if (!out->setFloatValue(in->getFloatValue()))
615 case SGPropertyNode::DOUBLE:
616 if (!out->setDoubleValue(in->getDoubleValue()))
619 case SGPropertyNode::STRING:
620 if (!out->setStringValue(in->getStringValue()))
623 case SGPropertyNode::UNSPECIFIED:
624 if (!out->setUnspecifiedValue(in->getStringValue()))
630 string message = "Unknown internal SGPropertyNode type";
631 message += in->getType();
632 throw sg_error(message, "SimGear Property Reader");
636 // copy the attributes.
637 out->setAttributes( in->getAttributes() );
639 // Next, copy the children.
640 int nChildren = in->nChildren();
641 for (int i = 0; i < nChildren; i++) {
642 const SGPropertyNode * in_child = in->getChild(i);
643 SGPropertyNode * out_child = out->getChild(in_child->getName(),
644 in_child->getIndex(),
646 if (!copyProperties(in_child, out_child))
653 // end of props_io.cxx