00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019 #include "xmlfile.h"
00020
00021 #ifdef STEPCORE_WITH_QT
00022
00023 #include "object.h"
00024 #include "world.h"
00025 #include "solver.h"
00026 #include "collisionsolver.h"
00027 #include "constraintsolver.h"
00028 #include "factory.h"
00029
00030 #include <QTextStream>
00031 #include <QMetaProperty>
00032 #include <QXmlDefaultHandler>
00033
00034 namespace StepCore {
00035
00036 const char* XmlFile::DOCKTYPE = "StepCoreXML";
00037 const char* XmlFile::NAMESPACE_URI = "http://edu.kde.org/step/StepCoreXML";
00038 const char* XmlFile::VERSION = "1.0";
00039
00040 namespace {
00041
00042 class StepStreamWriter
00043 {
00044 public:
00045 StepStreamWriter(QIODevice* device): _device(device) {}
00046 bool writeWorld(const World* world);
00047
00048 protected:
00049 QString escapeText(const QString& str);
00050 void saveProperties(const Object* obj, int first, int indent);
00051 void saveObject(const QString& tag, const Object* obj, int indent);
00052
00053 QIODevice* _device;
00054 QTextStream* _stream;
00055 QHash<const Object*, int> _ids;
00056 static const int INDENT = 4;
00057 };
00058
00059 QString StepStreamWriter::escapeText(const QString& str)
00060 {
00061 QString result = str;
00062 result.replace('&', "&");
00063 result.replace('<', "<");
00064 result.replace('>', ">");
00065 result.replace('\"', """);
00066 return result;
00067 }
00068
00069 void StepStreamWriter::saveProperties(const Object* obj, int first, int indent)
00070 {
00071 const MetaObject* metaObject = obj->metaObject();
00072 for(int i = first; i < metaObject->propertyCount(); ++i) {
00073 const MetaProperty* p = metaObject->property(i);
00074 if(p->isStored()) {
00075 *_stream << QString(indent*INDENT, ' ')
00076 << "<" << p->name() << ">";
00077
00078 if(p->userTypeId() == qMetaTypeId<Object*>())
00079 *_stream << _ids.value(p->readVariant(obj).value<Object*>(), -1);
00080 else *_stream << escapeText(p->readString(obj));
00081
00082 *_stream << "</" << p->name() << ">\n";
00083 }
00084 }
00085 }
00086
00087 void StepStreamWriter::saveObject(const QString& tag, const Object* obj, int indent)
00088 {
00089 Q_ASSERT(obj != NULL);
00090
00091 *_stream << QString(indent*INDENT, ' ') << "<" << tag
00092 << " class=\"" << QString(obj->metaObject()->className())
00093 << "\" id=\"" << _ids.value(obj, -1) << "\">\n";
00094
00095 saveProperties(obj, 0, indent+1);
00096
00097 if(obj->metaObject()->inherits<Item>()) {
00098 const ObjectErrors* objErrors = static_cast<const Item*>(obj)->tryGetObjectErrors();
00099 if(objErrors) saveProperties(objErrors, 1, indent+1);
00100 }
00101
00102 if(obj->metaObject()->inherits<ItemGroup>()) {
00103 const ItemGroup* group = static_cast<const ItemGroup*>(obj);
00104 *_stream << "\n";
00105 ItemList::const_iterator end = group->items().end();
00106 for(ItemList::const_iterator it = group->items().begin(); it != end; ++it) {
00107 saveObject("item", *it, indent+1);
00108 *_stream << "\n";
00109 }
00110 }
00111
00112 *_stream << QString(indent*INDENT, ' ') << "</" << tag << ">\n";
00113 }
00114
00115 bool StepStreamWriter::writeWorld(const World* world)
00116 {
00117 Q_ASSERT(_device->isOpen() && _device->isWritable());
00118 _stream = new QTextStream(_device);
00119
00120 int maxid = -1;
00121 _ids.insert(NULL, ++maxid);
00122 _ids.insert(world, ++maxid);
00123
00124 ItemList items = world->allItems();
00125 const ItemList::const_iterator end0 = items.end();
00126 for(ItemList::const_iterator it = items.begin(); it != end0; ++it)
00127 _ids.insert(*it, ++maxid);
00128
00129 if(world->solver()) _ids.insert(world->solver(), ++maxid);
00130 if(world->collisionSolver()) _ids.insert(world->collisionSolver(), ++maxid);
00131 if(world->constraintSolver()) _ids.insert(world->constraintSolver(), ++maxid);
00132
00133 *_stream << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
00134 << "<!DOCTYPE " << XmlFile::DOCKTYPE << ">\n"
00135 << "<world xmlns=\"" << XmlFile::NAMESPACE_URI << "\""
00136 << " version=\"" << XmlFile::VERSION << "\" id=\"1\">\n";
00137
00138 saveProperties(world, 0, 1);
00139 *_stream << "\n";
00140
00141 ItemList::const_iterator end = world->items().end();
00142 for(ItemList::const_iterator it = world->items().begin(); it != end; ++it) {
00143 saveObject("item", *it, 1);
00144 *_stream << "\n";
00145 }
00146
00147 if(world->solver()) {
00148 saveObject("solver", world->solver(), 1);
00149 *_stream << "\n";
00150 }
00151
00152 if(world->collisionSolver()) {
00153 saveObject("collisionSolver", world->collisionSolver(), 1);
00154 *_stream << "\n";
00155 }
00156
00157 if(world->constraintSolver()) {
00158 saveObject("constraintSolver", world->constraintSolver(), 1);
00159 *_stream << "\n";
00160 }
00161
00162 *_stream << "</world>\n";
00163
00164 delete _stream;
00165 return true;
00166 }
00167
00168 class XmlFileHandler: public QXmlDefaultHandler
00169 {
00170 public:
00171 XmlFileHandler(World* world, const Factory* factory);
00172
00173 bool startElement(const QString &namespaceURI, const QString &localName,
00174 const QString &qName, const QXmlAttributes &attributes);
00175 bool endElement(const QString &namespaceURI, const QString &localName,
00176 const QString &qName);
00177 bool characters(const QString &str);
00178 bool fatalError(const QXmlParseException &exception);
00179 bool endDocument();
00180 QString errorString() const;
00181
00182 protected:
00183 bool addId(Object* obj, const QString& id);
00184
00185 enum { START, ITEM, PROPERTY, END } _state;
00186 World* _world;
00187 const Factory* _factory;
00188
00189 ItemGroup* _parent;
00190 Object* _object;
00191 ObjectErrors* _objectErrors;
00192 const MetaProperty* _property;
00193
00194 typedef QPair<QPair<Object*, const MetaProperty*>, int> Link;
00195 QHash<int, Object*> _ids;
00196 QList<Link> _links;
00197
00198 QString _text;
00199 QString _errorString;
00200 };
00201
00202 XmlFileHandler::XmlFileHandler(World* world, const Factory* factory)
00203 : _state(START), _world(world), _factory(factory),
00204 _parent(NULL), _object(NULL), _objectErrors(NULL), _property(NULL)
00205 {
00206 }
00207
00208 bool XmlFileHandler::addId(Object* obj, const QString& id)
00209 {
00210
00211
00212
00213
00214
00215
00216 int n = id.trimmed().toInt();
00217 if(!n) {
00218 _errorString = QObject::tr("Wrong ID attribute value for %1")
00219 .arg(obj->metaObject()->className());
00220 return false;
00221 }
00222 if(_ids.contains(n)) {
00223 _errorString = QObject::tr("Non-unique ID attribute value for %1")
00224 .arg(obj->metaObject()->className());
00225 return false;
00226 }
00227
00228 _ids.insert(n, _object);
00229 return true;
00230 }
00231
00232 bool XmlFileHandler::startElement(const QString &namespaceURI, const QString &,
00233 const QString &qName, const QXmlAttributes &attributes)
00234 {
00235 if(namespaceURI != XmlFile::NAMESPACE_URI) return true;
00236
00237 switch(_state) {
00238 case START:
00239 if(qName == "world") {
00240 _parent = NULL;
00241 _object = _world;
00242 _state = ITEM;
00243
00244 if(!addId(_object, attributes.value("id"))) return false;
00245
00246 } else {
00247 _errorString = QObject::tr("The file is not a StepCoreXML file.");
00248 return false;
00249 }
00250 break;
00251
00252 case ITEM:
00253 if(qName == "item" && _object->metaObject()->inherits<ItemGroup>()) {
00254 _parent = static_cast<ItemGroup*>(_object);
00255
00256 QString className = attributes.value("class");
00257 Item* item = _factory->newItem(className);
00258 if(item == NULL) {
00259 _errorString = QObject::tr("Unknown item type \"%1\"").arg(className);
00260 return false;
00261 }
00262
00263 _parent->addItem(item);
00264 _object = item;
00265
00266 if(!addId(_object, attributes.value("id"))) return false;
00267
00268 break;
00269
00270 } else if(_object == _world && qName == "solver") {
00271 Solver* solver = _factory->newSolver(attributes.value("class"));
00272 if(solver == NULL) {
00273 _errorString = QObject::tr("Unknown solver type \"%1\"").arg(attributes.value("class"));
00274 return false;
00275 }
00276
00277 _world->setSolver(solver);
00278 _object = solver;
00279 _parent = _world;
00280
00281 if(!addId(_object, attributes.value("id"))) return false;
00282
00283 break;
00284
00285 } else if(_object == _world && qName == "collisionSolver") {
00286 CollisionSolver* collisionSolver = _factory->newCollisionSolver(attributes.value("class"));
00287 if(collisionSolver == NULL) {
00288 _errorString = QObject::tr("Unknown collisionSolver type \"%1\"").arg(attributes.value("class"));
00289 return false;
00290 }
00291
00292 _world->setCollisionSolver(collisionSolver);
00293 _object = collisionSolver;
00294 _parent = _world;
00295
00296 if(!addId(_object, attributes.value("id"))) return false;
00297
00298 break;
00299
00300 } else if(_object == _world && qName == "constraintSolver") {
00301 ConstraintSolver* constraintSolver = _factory->newConstraintSolver(attributes.value("class"));
00302 if(constraintSolver == NULL) {
00303 _errorString = QObject::tr("Unknown constraintSolver type \"%1\"").arg(attributes.value("class"));
00304 return false;
00305 }
00306
00307 _world->setConstraintSolver(constraintSolver);
00308 _object = constraintSolver;
00309 _parent = _world;
00310
00311 if(!addId(_object, attributes.value("id"))) return false;
00312
00313 break;
00314
00315 }
00316
00317
00318 _property = _object->metaObject()->property(qName);
00319 if(!_property && _object->metaObject()->inherits<Item>()) {
00320 const MetaObject* objErrors = _factory->metaObject(_object->metaObject()->className()+"Errors");
00321 if(objErrors) {
00322 _property = objErrors->property(qName);
00323 if(_property && _property->isStored())
00324 _objectErrors = static_cast<Item*>(_object)->objectErrors();
00325 }
00326 }
00327
00328 if(!_property || !_property->isStored()) {
00329 _errorString = QObject::tr("Item \"%1\" has no stored property named \"%2\"")
00330 .arg(QString(_object->metaObject()->className())).arg(qName);
00331 return false;
00332 }
00333
00334 _text.clear();
00335 _state = PROPERTY;
00336 break;
00337
00338 case PROPERTY:
00339 default:
00340 _errorString = QObject::tr("Unexpected tag \"%1\"").arg(qName);
00341 return false;
00342 }
00343
00344 return true;
00345 }
00346
00347 bool XmlFileHandler::endElement(const QString &namespaceURI, const QString &,
00348 const QString &qName)
00349 {
00350 if(namespaceURI != XmlFile::NAMESPACE_URI) return true;
00351
00352 switch(_state) {
00353 case PROPERTY:
00354 if(_property->userTypeId() == qMetaTypeId<Object*>()) {
00355
00356
00357
00358
00359
00360
00361
00362
00363
00364 {
00365 int n = _text.trimmed().toInt();
00366 _links.push_back(qMakePair(qMakePair(
00367 static_cast<Object*>(_objectErrors ? _objectErrors : _object),
00368 _property), n));
00369 }
00370 }
00371 else if(!_property->writeString(_objectErrors ? _objectErrors : _object, _text)) {
00372 _errorString = QObject::tr("Property \"%1\" of \"%2\" has illegal value")
00373 .arg(qName, _object->metaObject()->className());
00374 return false;
00375 }
00376 _objectErrors = NULL;
00377 _state = ITEM;
00378 break;
00379
00380 case ITEM:
00381 if(_parent == NULL) {
00382 STEPCORE_ASSERT_NOABORT(_object == _world);
00383 _state = END;
00384 } else {
00385 STEPCORE_ASSERT_NOABORT(_parent->metaObject()->inherits<Item>());
00386 Item* item = static_cast<Item*>(_parent);
00387 _object = _parent;
00388 _parent = item->group();
00389 }
00390 break;
00391
00392 default:
00393 STEPCORE_ASSERT_NOABORT(false);
00394 }
00395
00396 return true;
00397 }
00398
00399 bool XmlFileHandler::characters(const QString &str)
00400 {
00401 if(_state == PROPERTY) _text += str;
00402 return true;
00403 }
00404
00405 bool XmlFileHandler::endDocument()
00406 {
00407 if(_state != END) {
00408 _errorString = QObject::tr("\"world\" tag not found");
00409 return false;
00410 }
00411
00412
00413 foreach(const Link& link, _links) {
00414 QVariant target = QVariant::fromValue(_ids.value(link.second, NULL));
00415 if(!link.first.second->writeVariant(link.first.first, target)) {
00416 _errorString = QObject::tr("Property \"%1\" of \"%2\" has illegal value")
00417 .arg(link.first.second->name(), link.first.first->metaObject()->className());
00418 return false;
00419 }
00420 }
00421
00422 return true;
00423 }
00424
00425 bool XmlFileHandler::fatalError(const QXmlParseException &exception)
00426 {
00427 _errorString = QObject::tr("Error parsing file at line %1: %2")
00428 .arg(exception.lineNumber()).arg(exception.message());
00429 return false;
00430 }
00431
00432 QString XmlFileHandler::errorString() const
00433 {
00434 return _errorString;
00435 }
00436
00437 }
00438
00439 bool XmlFile::save(const World* world)
00440 {
00441 if(!_device->isOpen() || !_device-> isWritable()) {
00442 _errorString = QObject::tr("File is not writable.");
00443 return false;
00444 }
00445
00446 StepStreamWriter writer(_device);
00447 return writer.writeWorld(world);
00448 }
00449
00450 bool XmlFile::load(World* world, const Factory* factory)
00451 {
00452 XmlFileHandler handler(world, factory);
00453 QXmlInputSource source(_device);
00454 QXmlSimpleReader reader;
00455
00456 reader.setContentHandler(&handler);
00457 reader.setErrorHandler(&handler);
00458 if(reader.parse(source)) return true;
00459 _errorString = handler.errorString();
00460 return false;
00461 }
00462
00463 }
00464
00465 #endif //STEPCORE_WITH_QT
00466