Warning, file /education/step/stepcore/xmlfile.cc was not indexed or was modified since last indexation (in which case cross-reference links may be missing, inaccurate or erroneous).

0001 /*
0002     SPDX-FileCopyrightText: 2007 Vladimir Kuznetsov <ks.vladimir@gmail.com>
0003 
0004     SPDX-License-Identifier: GPL-2.0-or-later
0005 */
0006 
0007 #include "xmlfile.h"
0008 
0009 #ifdef STEPCORE_WITH_QT
0010 
0011 #include "world.h"
0012 #include "solver.h"
0013 #include "collisionsolver.h"
0014 #include "constraintsolver.h"
0015 #include "factory.h"
0016 
0017 #include <QDomDocument>
0018 #include <QXmlStreamWriter>
0019 
0020 namespace StepCore {
0021 
0022 const char* XmlFile::DOCTYPE = "<!DOCTYPE StepCoreXML>";
0023 const char* XmlFile::NAMESPACE_URI = "https://edu.kde.org/step/StepCoreXML";
0024 const char* XmlFile::VERSION = "1.0";
0025 
0026 namespace {
0027 
0028 class StepStreamWriter
0029 {
0030 public:
0031     StepStreamWriter(QIODevice* device);
0032     bool writeWorld(const World* world);
0033 
0034 protected:
0035     void saveProperties(const Object* obj, int first);
0036     void saveObject(const QString& tag, const Object* obj);
0037 
0038     QXmlStreamWriter _writer;
0039     QIODevice* _device;
0040     QHash<const Object*, int> _ids;
0041     static const int INDENT = 4;
0042 };
0043 
0044 StepStreamWriter::StepStreamWriter(QIODevice* device) : _device(device)
0045 {
0046     _writer.setAutoFormatting(true);
0047     _writer.setAutoFormattingIndent(INDENT);
0048 }
0049 
0050 void StepStreamWriter::saveProperties(const Object* obj, int first)
0051 {
0052     const MetaObject* metaObject = obj->metaObject();
0053     for(int i = first; i < metaObject->propertyCount(); ++i) {
0054         const MetaProperty* p = metaObject->property(i);
0055         if(p->isStored()) {
0056             if(p->userTypeId() == qMetaTypeId<Object*>()) {
0057                 int id = _ids.value(p->readVariant(obj).value<Object*>(), -1);
0058                 _writer.writeTextElement(p->name(), QString::number(id));
0059             }
0060             else {
0061                 _writer.writeTextElement(p->name(), p->readString(obj));
0062             }
0063         }
0064     }
0065 }
0066 
0067 void StepStreamWriter::saveObject(const QString& tag, const Object* obj)
0068 {
0069     Q_ASSERT(obj != nullptr);
0070     
0071     _writer.writeStartElement(tag);
0072     _writer.writeAttribute(QStringLiteral("class"), obj->metaObject()->className());
0073     _writer.writeAttribute(QStringLiteral("id"), QString::number(_ids.value(obj, -1)));
0074 
0075     saveProperties(obj, 0);
0076 
0077     if(obj->metaObject()->inherits<Item>()) {
0078         const ObjectErrors* objErrors = static_cast<const Item*>(obj)->tryGetObjectErrors();
0079         if(objErrors) saveProperties(objErrors, 1);
0080     }
0081 
0082     if(obj->metaObject()->inherits<ItemGroup>()) {
0083         const ItemGroup* group = static_cast<const ItemGroup*>(obj);
0084         ItemList::const_iterator end = group->items().end();
0085         for(ItemList::const_iterator it = group->items().begin(); it != end; ++it) {
0086             saveObject(QStringLiteral("item"), *it);
0087         }
0088     }
0089     
0090     _writer.writeEndElement();
0091 }
0092 
0093 bool StepStreamWriter::writeWorld(const World* world)
0094 {
0095     Q_ASSERT(_device->isOpen() && _device->isWritable());
0096     _writer.setDevice(_device);
0097 
0098     int maxid = -1;
0099     _ids.insert(NULL, ++maxid);
0100     _ids.insert(world, ++maxid);
0101 
0102     ItemList items = world->allItems();
0103     const ItemList::const_iterator end0 = items.end();
0104     for(ItemList::const_iterator it = items.begin(); it != end0; ++it)
0105         _ids.insert(*it, ++maxid);
0106 
0107     if(world->solver()) _ids.insert(world->solver(), ++maxid);
0108     if(world->collisionSolver()) _ids.insert(world->collisionSolver(), ++maxid);
0109     if(world->constraintSolver()) _ids.insert(world->constraintSolver(), ++maxid);
0110 
0111     _writer.writeStartDocument();
0112     _writer.writeDTD(XmlFile::DOCTYPE);
0113     _writer.writeStartElement(QStringLiteral("world"));
0114     _writer.writeAttribute(QStringLiteral("xmlns"), XmlFile::NAMESPACE_URI);
0115     _writer.writeAttribute(QStringLiteral("version"), XmlFile::VERSION);
0116     _writer.writeAttribute(QStringLiteral("id"), QStringLiteral("1"));
0117 
0118     saveProperties(world, 0);
0119 
0120     ItemList::const_iterator end = world->items().end();
0121     for(ItemList::const_iterator it = world->items().begin(); it != end; ++it) {
0122         saveObject(QStringLiteral("item"), *it);
0123     }
0124 
0125     if(world->solver()) {
0126         saveObject(QStringLiteral("solver"), world->solver());
0127     }
0128 
0129     if(world->collisionSolver()) {
0130         saveObject(QStringLiteral("collisionSolver"), world->collisionSolver());
0131     }
0132 
0133     if(world->constraintSolver()) {
0134         saveObject(QStringLiteral("constraintSolver"), world->constraintSolver());
0135     }
0136     
0137     _writer.writeEndElement();
0138     _writer.writeEndDocument();
0139     
0140     return true;
0141 }
0142 
0143 class StepDomDocument
0144 {
0145 public:
0146     StepDomDocument(World* world, const Factory* factory);
0147     
0148     bool parse(QIODevice* device);
0149     
0150     const QString& errorMsg() const { return _errorMsg; }
0151     
0152 private:
0153     typedef QPair<QPair<Object*, const MetaProperty*>, int> Link;
0154     
0155     Item* createItem(const QDomElement& element);
0156     Solver* createSolver(const QDomElement& element);
0157     CollisionSolver* createCollisionSolver(const QDomElement& element);
0158     ConstraintSolver* createConstraintSolver(const QDomElement& element);
0159     bool parseWorld(const QDomElement& element);
0160     bool parseItems(ItemGroup* parent, const QDomElement& element);
0161     bool parseObject(Object* object, const QDomElement& element);
0162     bool parseProperties(Object* object, const QDomElement& parent);
0163     bool connectLinks();
0164     
0165     World* _world;
0166     const Factory* _factory;
0167     QDomDocument _document;
0168     QString _errorMsg;
0169     int _errorLine;
0170     int _errorCount;
0171     QString _version;
0172     QHash<int, Object*> _ids;
0173     QList<Link> _links;
0174 };
0175 
0176 StepDomDocument::StepDomDocument(World* world, const StepCore::Factory* factory) :
0177     _world(world), _factory(factory), _errorLine(0), _errorCount(0)
0178 {
0179 }
0180 
0181 bool StepDomDocument::parse(QIODevice* device)
0182 {
0183     if (!_document.setContent(device, &_errorMsg, &_errorLine, &_errorCount)) {
0184         return false;
0185     }
0186     
0187     QDomElement worldElement = _document.firstChildElement(QStringLiteral("world"));
0188     if (worldElement.isNull()) {
0189         _errorMsg = QObject::tr("The file is not a StepCoreXML file.");
0190         return false;
0191     }
0192     
0193     return parseWorld(worldElement);
0194 }
0195 
0196 bool StepDomDocument::parseWorld(const QDomElement& world)
0197 {
0198     _version = world.attribute(QStringLiteral("version"), QStringLiteral("1.0"));
0199     
0200     if (!parseObject(_world, world)) return false;
0201     if (!parseItems(_world, world)) return false;
0202     
0203     QDomElement solverElement = world.firstChildElement(QStringLiteral("solver"));
0204     if (!solverElement.isNull()) {
0205         Solver *solver = createSolver(solverElement);
0206         if (!solver) return false;
0207         
0208         _world->setSolver(solver);
0209     }
0210     
0211     QDomElement collisionSolverElement = world.firstChildElement(QStringLiteral("collisionSolver"));
0212     if (!collisionSolverElement.isNull()) {
0213         CollisionSolver *solver = createCollisionSolver(collisionSolverElement);
0214         if (!solver) return false;
0215         
0216         _world->setCollisionSolver(solver);
0217     }
0218     
0219     QDomElement constraintSolverElement = world.firstChildElement(QStringLiteral("constraintSolver"));
0220     if (!constraintSolverElement.isNull()) {
0221         ConstraintSolver *solver = createConstraintSolver(constraintSolverElement);
0222         if (!solver) return false;
0223         
0224         _world->setConstraintSolver(solver);
0225     }
0226     
0227     return connectLinks();
0228 }
0229 
0230 Item* StepDomDocument::createItem(const QDomElement& element)
0231 {
0232     QString className = element.attribute(QStringLiteral("class"));
0233     QScopedPointer<Item> item(_factory->newItem(className));
0234     if (!item) {
0235         _errorMsg = QObject::tr("Unknown item type \"%1\"").arg(className);
0236         return nullptr;
0237     }
0238     
0239     if (!parseObject(item.data(), element)) return nullptr;
0240     ObjectErrors *objErrors = item->objectErrors();
0241     if (objErrors && !parseProperties(objErrors, element)) return nullptr;
0242     
0243     if (item->metaObject()->inherits("ItemGroup")) {
0244         ItemGroup *group = static_cast<ItemGroup*>(item.data());
0245         if (!parseItems(group, element)) return nullptr;
0246     }
0247 
0248     return item.take();
0249 }
0250 
0251 Solver* StepDomDocument::createSolver(const QDomElement& element)
0252 {
0253     QString className = element.attribute(QStringLiteral("class"));
0254     QScopedPointer<Solver> solver(_factory->newSolver(className));
0255     if (!solver) {
0256         _errorMsg = QObject::tr("Unknown solver type \"%1\"").arg(className);
0257         return nullptr;
0258     }
0259     
0260     if (!parseObject(solver.data(), element)) return nullptr;
0261     
0262     return solver.take();
0263 }
0264 
0265 CollisionSolver* StepDomDocument::createCollisionSolver(const QDomElement& element)
0266 {
0267     QString className = element.attribute(QStringLiteral("class"));
0268     QScopedPointer<CollisionSolver> solver(_factory->newCollisionSolver(className));
0269     if (!solver) {
0270         _errorMsg = QObject::tr("Unknown collisionSolver type \"%1\"").arg(className);
0271         return nullptr;
0272     }
0273     
0274     if (!parseObject(solver.data(), element)) return nullptr;
0275     
0276     return solver.take();
0277 }
0278 
0279 ConstraintSolver* StepDomDocument::createConstraintSolver(const QDomElement& element)
0280 {
0281     QString className = element.attribute(QStringLiteral("class"));
0282     QScopedPointer<ConstraintSolver> solver(_factory->newConstraintSolver(className));
0283     if (!solver) {
0284         _errorMsg = QObject::tr("Unknown constraint solver type \"%1\"").arg(className);
0285         return nullptr;
0286     }
0287     
0288     if (!parseObject(solver.data(), element)) return nullptr;
0289     
0290     return solver.take();
0291 }
0292 
0293 bool StepDomDocument::parseItems(ItemGroup* parent, const QDomElement& element)
0294 {
0295     QDomElement itemElement = element.firstChildElement(QStringLiteral("item"));
0296     while (!itemElement.isNull()) {
0297         Item *item = createItem(itemElement);
0298         if (!item) return false;
0299         
0300         parent->addItem(item);
0301         itemElement = itemElement.nextSiblingElement(QStringLiteral("item"));
0302     }
0303     
0304     return true;
0305 }
0306 
0307 bool StepDomDocument::parseObject(Object* object, const QDomElement& element)
0308 {
0309     int n = element.attribute(QStringLiteral("id")).trimmed().toInt();
0310     
0311     if (!n) {
0312         _errorMsg = QObject::tr("Wrong ID attribute value for %1")
0313         .arg(object->metaObject()->className());
0314         return false;
0315     }
0316     if (_ids.contains(n)) {
0317         _errorMsg = QObject::tr("Non-unique ID attribute value for %1")
0318         .arg(object->metaObject()->className());
0319         return false;
0320     }
0321     
0322     _ids.insert(n, object);
0323     
0324     return parseProperties(object, element);
0325 }
0326 
0327 bool StepDomDocument::parseProperties(Object* object, const QDomElement& parent)
0328 {
0329     int properties = object->metaObject()->propertyCount();
0330     for (int n = 0; n < properties; ++n) {
0331         const MetaProperty* property = object->metaObject()->property(n);
0332         
0333         if (!property->isStored()) continue;
0334         
0335         QString name = property->name();
0336         QDomElement propertyElement = parent.firstChildElement(name);
0337         if (propertyElement.isNull()) continue;
0338         
0339         QString text = propertyElement.text();
0340         if (property->userTypeId() == qMetaTypeId<Object*>()) {
0341             int n = text.trimmed().toInt();
0342             _links.push_back(qMakePair(qMakePair(object, property), n));
0343         }
0344         else if (!property->writeString(object, text)) {
0345             _errorMsg = QObject::tr("Property \"%1\" of \"%2\" has illegal value")
0346                 .arg(name, object->metaObject()->className());
0347             return false;
0348         }
0349     }
0350     
0351     return true;
0352 }
0353 
0354 bool StepDomDocument::connectLinks()
0355 {
0356     foreach (const Link& link, _links) {
0357         QVariant target = QVariant::fromValue(_ids.value(link.second, nullptr));
0358         if (!link.first.second->writeVariant(link.first.first, target)) {
0359             _errorMsg = QObject::tr("Property \"%1\" of \"%2\" has illegal value")
0360                 .arg(link.first.second->name(), link.first.first->metaObject()->className());
0361             return false;
0362         }
0363     }
0364     
0365     return true;
0366 }
0367 } // namespace
0368 
0369 bool XmlFile::save(const World* world)
0370 {
0371     if(!_device->isOpen() || !_device-> isWritable()) {
0372         _errorString = QObject::tr("File is not writable.");
0373         return false;
0374     }
0375 
0376     StepStreamWriter writer(_device);
0377     return writer.writeWorld(world);
0378 }
0379 
0380 bool XmlFile::load(World* world, const Factory* factory)
0381 {
0382     StepDomDocument document(world, factory);
0383     if (!document.parse(_device)) {
0384         _errorString = document.errorMsg();
0385         return false;
0386     }
0387     
0388     return true;
0389 }
0390 } // namespace StepCore
0391 
0392 #endif //STEPCORE_WITH_QT
0393