/* This file is part of StepCore library. Copyright (C) 2007 Vladimir Kuznetsov StepCore library is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. StepCore library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with StepCore; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include "xmlfile.h" #ifdef STEPCORE_WITH_QT #include "object.h" #include "world.h" #include "solver.h" #include "collisionsolver.h" #include "constraintsolver.h" #include "factory.h" #include #include #include namespace StepCore { const char* XmlFile::DOCKTYPE = "StepCoreXML"; const char* XmlFile::NAMESPACE_URI = "http://edu.kde.org/step/StepCoreXML"; const char* XmlFile::VERSION = "1.0"; namespace { class StepStreamWriter { public: StepStreamWriter(QIODevice* device): _device(device) {} bool writeWorld(const World* world); protected: QString escapeText(const QString& str); void saveProperties(const Object* obj, int first, int indent); void saveObject(const QString& tag, const Object* obj, int indent); QIODevice* _device; QTextStream* _stream; QHash _ids; static const int INDENT = 4; }; QString StepStreamWriter::escapeText(const QString& str) { QString result = str; result.replace('&', "&"); result.replace('<', "<"); result.replace('>', ">"); result.replace('\"', """); return result; } void StepStreamWriter::saveProperties(const Object* obj, int first, int indent) { const MetaObject* metaObject = obj->metaObject(); for(int i = first; i < metaObject->propertyCount(); ++i) { const MetaProperty* p = metaObject->property(i); if(p->isStored()) { *_stream << QString(indent*INDENT, ' ') << "<" << p->name() << ">"; if(p->userTypeId() == qMetaTypeId()) *_stream << _ids.value(p->readVariant(obj).value(), -1); else *_stream << escapeText(p->readString(obj)); *_stream << "name() << ">\n"; } } } void StepStreamWriter::saveObject(const QString& tag, const Object* obj, int indent) { Q_ASSERT(obj != NULL); *_stream << QString(indent*INDENT, ' ') << "<" << tag << " class=\"" << QString(obj->metaObject()->className()) << "\" id=\"" << _ids.value(obj, -1) << "\">\n"; saveProperties(obj, 0, indent+1); if(obj->metaObject()->inherits()) { const ObjectErrors* objErrors = static_cast(obj)->tryGetObjectErrors(); if(objErrors) saveProperties(objErrors, 1, indent+1); } if(obj->metaObject()->inherits()) { const ItemGroup* group = static_cast(obj); *_stream << "\n"; ItemList::const_iterator end = group->items().end(); for(ItemList::const_iterator it = group->items().begin(); it != end; ++it) { saveObject("item", *it, indent+1); *_stream << "\n"; } } *_stream << QString(indent*INDENT, ' ') << "\n"; } bool StepStreamWriter::writeWorld(const World* world) { Q_ASSERT(_device->isOpen() && _device->isWritable()); _stream = new QTextStream(_device); int maxid = -1; _ids.insert(NULL, ++maxid); _ids.insert(world, ++maxid); ItemList items = world->allItems(); const ItemList::const_iterator end0 = items.end(); for(ItemList::const_iterator it = items.begin(); it != end0; ++it) _ids.insert(*it, ++maxid); if(world->solver()) _ids.insert(world->solver(), ++maxid); if(world->collisionSolver()) _ids.insert(world->collisionSolver(), ++maxid); if(world->constraintSolver()) _ids.insert(world->constraintSolver(), ++maxid); *_stream << "\n" << "\n" << "\n"; saveProperties(world, 0, 1); *_stream << "\n"; ItemList::const_iterator end = world->items().end(); for(ItemList::const_iterator it = world->items().begin(); it != end; ++it) { saveObject("item", *it, 1); *_stream << "\n"; } if(world->solver()) { saveObject("solver", world->solver(), 1); *_stream << "\n"; } if(world->collisionSolver()) { saveObject("collisionSolver", world->collisionSolver(), 1); *_stream << "\n"; } if(world->constraintSolver()) { saveObject("constraintSolver", world->constraintSolver(), 1); *_stream << "\n"; } *_stream << "\n"; delete _stream; return true; } class XmlFileHandler: public QXmlDefaultHandler { public: XmlFileHandler(World* world, const Factory* factory); bool startElement(const QString &namespaceURI, const QString &localName, const QString &qName, const QXmlAttributes &attributes); bool endElement(const QString &namespaceURI, const QString &localName, const QString &qName); bool characters(const QString &str); bool fatalError(const QXmlParseException &exception); bool endDocument(); QString errorString() const; protected: bool addId(Object* obj, const QString& id); enum { START, ITEM, PROPERTY, END } _state; World* _world; const Factory* _factory; ItemGroup* _parent; Object* _object; ObjectErrors* _objectErrors; const MetaProperty* _property; typedef QPair, int> Link; QHash _ids; QList _links; QString _text; QString _errorString; }; XmlFileHandler::XmlFileHandler(World* world, const Factory* factory) : _state(START), _world(world), _factory(factory), _parent(NULL), _object(NULL), _objectErrors(NULL), _property(NULL) { } bool XmlFileHandler::addId(Object* obj, const QString& id) { /* #ifdef __GNUC__ #warning Temporary code #endif if(id.isEmpty()) return true; */ int n = id.trimmed().toInt(); if(!n) { _errorString = QObject::tr("Wrong ID attribute value for %1") .arg(obj->metaObject()->className()); return false; } if(_ids.contains(n)) { _errorString = QObject::tr("Non-unique ID attribute value for %1") .arg(obj->metaObject()->className()); return false; } _ids.insert(n, _object); return true; } bool XmlFileHandler::startElement(const QString &namespaceURI, const QString &, const QString &qName, const QXmlAttributes &attributes) { if(namespaceURI != XmlFile::NAMESPACE_URI) return true; // XXX: is it correct behaviour ? switch(_state) { case START: if(qName == "world") { _parent = NULL; _object = _world; _state = ITEM; if(!addId(_object, attributes.value("id"))) return false; } else { _errorString = QObject::tr("The file is not a StepCoreXML file."); return false; } break; case ITEM: if(qName == "item" && _object->metaObject()->inherits()) { _parent = static_cast(_object); QString className = attributes.value("class"); Item* item = _factory->newItem(className); if(item == NULL) { _errorString = QObject::tr("Unknown item type \"%1\"").arg(className); return false; } _parent->addItem(item); _object = item; if(!addId(_object, attributes.value("id"))) return false; break; } else if(_object == _world && qName == "solver") { Solver* solver = _factory->newSolver(attributes.value("class")); if(solver == NULL) { _errorString = QObject::tr("Unknown solver type \"%1\"").arg(attributes.value("class")); return false; } _world->setSolver(solver); _object = solver; _parent = _world; if(!addId(_object, attributes.value("id"))) return false; break; } else if(_object == _world && qName == "collisionSolver") { CollisionSolver* collisionSolver = _factory->newCollisionSolver(attributes.value("class")); if(collisionSolver == NULL) { _errorString = QObject::tr("Unknown collisionSolver type \"%1\"").arg(attributes.value("class")); return false; } _world->setCollisionSolver(collisionSolver); _object = collisionSolver; _parent = _world; if(!addId(_object, attributes.value("id"))) return false; break; } else if(_object == _world && qName == "constraintSolver") { ConstraintSolver* constraintSolver = _factory->newConstraintSolver(attributes.value("class")); if(constraintSolver == NULL) { _errorString = QObject::tr("Unknown constraintSolver type \"%1\"").arg(attributes.value("class")); return false; } _world->setConstraintSolver(constraintSolver); _object = constraintSolver; _parent = _world; if(!addId(_object, attributes.value("id"))) return false; break; } _property = _object->metaObject()->property(qName); if(!_property && _object->metaObject()->inherits()) { const MetaObject* objErrors = _factory->metaObject(_object->metaObject()->className()+"Errors"); if(objErrors) { _property = objErrors->property(qName); if(_property && _property->isStored()) _objectErrors = static_cast(_object)->objectErrors(); } } if(!_property || !_property->isStored()) { _errorString = QObject::tr("Item \"%1\" has no stored property named \"%2\"") .arg(QString(_object->metaObject()->className())).arg(qName); return false; } _text.clear(); _state = PROPERTY; break; case PROPERTY: default: _errorString = QObject::tr("Unexpected tag \"%1\"").arg(qName); return false; } return true; } bool XmlFileHandler::endElement(const QString &namespaceURI, const QString &, const QString &qName) { if(namespaceURI != XmlFile::NAMESPACE_URI) return true; // XXX: is it correct behaviour ? switch(_state) { case PROPERTY: if(_property->userTypeId() == qMetaTypeId()) { /* #ifdef __GNUC__ #warning Temporary code #endif if(!_text.trimmed()[0].isDigit()) { Object* o = _world->object(_text); qDebug("deprecated link to %s (%p)", _text.toLatin1().constData(), o); QVariant obj = QVariant::fromValue(o); _property->writeVariant(_object, obj); } else*/ { int n = _text.trimmed().toInt(); _links.push_back(qMakePair(qMakePair( static_cast(_objectErrors ? _objectErrors : _object), _property), n)); } } else if(!_property->writeString(_objectErrors ? _objectErrors : _object, _text)) { _errorString = QObject::tr("Property \"%1\" of \"%2\" has illegal value") .arg(qName, _object->metaObject()->className()); return false; } _objectErrors = NULL; _state = ITEM; break; case ITEM: if(_parent == NULL) { STEPCORE_ASSERT_NOABORT(_object == _world); _state = END; } else { STEPCORE_ASSERT_NOABORT(_parent->metaObject()->inherits()); Item* item = static_cast(_parent); _object = _parent; _parent = item->group(); } break; default: STEPCORE_ASSERT_NOABORT(false); } return true; } bool XmlFileHandler::characters(const QString &str) { if(_state == PROPERTY) _text += str; return true; } bool XmlFileHandler::endDocument() { if(_state != END) { _errorString = QObject::tr("\"world\" tag not found"); return false; } // Connect links foreach(const Link& link, _links) { QVariant target = QVariant::fromValue(_ids.value(link.second, NULL)); if(!link.first.second->writeVariant(link.first.first, target)) { _errorString = QObject::tr("Property \"%1\" of \"%2\" has illegal value") .arg(link.first.second->name(), link.first.first->metaObject()->className()); return false; } } return true; } bool XmlFileHandler::fatalError(const QXmlParseException &exception) { _errorString = QObject::tr("Error parsing file at line %1: %2") .arg(exception.lineNumber()).arg(exception.message()); return false; } QString XmlFileHandler::errorString() const { return _errorString; } } // namespace bool XmlFile::save(const World* world) { if(!_device->isOpen() || !_device-> isWritable()) { _errorString = QObject::tr("File is not writable."); return false; } StepStreamWriter writer(_device); return writer.writeWorld(world); } bool XmlFile::load(World* world, const Factory* factory) { XmlFileHandler handler(world, factory); QXmlInputSource source(_device); QXmlSimpleReader reader; reader.setContentHandler(&handler); reader.setErrorHandler(&handler); if(reader.parse(source)) return true; _errorString = handler.errorString(); return false; } } // namespace StepCore #endif //STEPCORE_WITH_QT