File indexing completed on 2024-04-14 03:49:29

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     Q_ASSERT(device->isOpen() && device->isReadable());
0184 
0185     if (!_document.setContent(device, &_errorMsg, &_errorLine, &_errorCount)) {
0186         return false;
0187     }
0188     
0189     QDomElement worldElement = _document.firstChildElement(QStringLiteral("world"));
0190     if (worldElement.isNull()) {
0191         _errorMsg = QObject::tr("The file is not a StepCoreXML file.");
0192         return false;
0193     }
0194     
0195     return parseWorld(worldElement);
0196 }
0197 
0198 bool StepDomDocument::parseWorld(const QDomElement& world)
0199 {
0200     _version = world.attribute(QStringLiteral("version"), QStringLiteral("1.0"));
0201     
0202     if (!parseObject(_world, world)) return false;
0203     if (!parseItems(_world, world)) return false;
0204     
0205     QDomElement solverElement = world.firstChildElement(QStringLiteral("solver"));
0206     if (!solverElement.isNull()) {
0207         Solver *solver = createSolver(solverElement);
0208         if (!solver) return false;
0209         
0210         _world->setSolver(solver);
0211     }
0212     
0213     QDomElement collisionSolverElement = world.firstChildElement(QStringLiteral("collisionSolver"));
0214     if (!collisionSolverElement.isNull()) {
0215         CollisionSolver *solver = createCollisionSolver(collisionSolverElement);
0216         if (!solver) return false;
0217         
0218         _world->setCollisionSolver(solver);
0219     }
0220     
0221     QDomElement constraintSolverElement = world.firstChildElement(QStringLiteral("constraintSolver"));
0222     if (!constraintSolverElement.isNull()) {
0223         ConstraintSolver *solver = createConstraintSolver(constraintSolverElement);
0224         if (!solver) return false;
0225         
0226         _world->setConstraintSolver(solver);
0227     }
0228     
0229     return connectLinks();
0230 }
0231 
0232 Item* StepDomDocument::createItem(const QDomElement& element)
0233 {
0234     QString className = element.attribute(QStringLiteral("class"));
0235     QScopedPointer<Item> item(_factory->newItem(className));
0236     if (!item) {
0237         _errorMsg = QObject::tr("Unknown item type \"%1\"").arg(className);
0238         return nullptr;
0239     }
0240     
0241     if (!parseObject(item.data(), element)) return nullptr;
0242     ObjectErrors *objErrors = item->objectErrors();
0243     if (objErrors && !parseProperties(objErrors, element)) return nullptr;
0244     
0245     if (item->metaObject()->inherits("ItemGroup")) {
0246         ItemGroup *group = static_cast<ItemGroup*>(item.data());
0247         if (!parseItems(group, element)) return nullptr;
0248     }
0249 
0250     return item.take();
0251 }
0252 
0253 Solver* StepDomDocument::createSolver(const QDomElement& element)
0254 {
0255     QString className = element.attribute(QStringLiteral("class"));
0256     QScopedPointer<Solver> solver(_factory->newSolver(className));
0257     if (!solver) {
0258         _errorMsg = QObject::tr("Unknown solver type \"%1\"").arg(className);
0259         return nullptr;
0260     }
0261     
0262     if (!parseObject(solver.data(), element)) return nullptr;
0263     
0264     return solver.take();
0265 }
0266 
0267 CollisionSolver* StepDomDocument::createCollisionSolver(const QDomElement& element)
0268 {
0269     QString className = element.attribute(QStringLiteral("class"));
0270     QScopedPointer<CollisionSolver> solver(_factory->newCollisionSolver(className));
0271     if (!solver) {
0272         _errorMsg = QObject::tr("Unknown collisionSolver type \"%1\"").arg(className);
0273         return nullptr;
0274     }
0275     
0276     if (!parseObject(solver.data(), element)) return nullptr;
0277     
0278     return solver.take();
0279 }
0280 
0281 ConstraintSolver* StepDomDocument::createConstraintSolver(const QDomElement& element)
0282 {
0283     QString className = element.attribute(QStringLiteral("class"));
0284     QScopedPointer<ConstraintSolver> solver(_factory->newConstraintSolver(className));
0285     if (!solver) {
0286         _errorMsg = QObject::tr("Unknown constraint solver type \"%1\"").arg(className);
0287         return nullptr;
0288     }
0289     
0290     if (!parseObject(solver.data(), element)) return nullptr;
0291     
0292     return solver.take();
0293 }
0294 
0295 bool StepDomDocument::parseItems(ItemGroup* parent, const QDomElement& element)
0296 {
0297     QDomElement itemElement = element.firstChildElement(QStringLiteral("item"));
0298     while (!itemElement.isNull()) {
0299         Item *item = createItem(itemElement);
0300         if (!item) return false;
0301         
0302         parent->addItem(item);
0303         itemElement = itemElement.nextSiblingElement(QStringLiteral("item"));
0304     }
0305     
0306     return true;
0307 }
0308 
0309 bool StepDomDocument::parseObject(Object* object, const QDomElement& element)
0310 {
0311     int n = element.attribute(QStringLiteral("id")).trimmed().toInt();
0312     
0313     if (!n) {
0314         _errorMsg = QObject::tr("Wrong ID attribute value for %1")
0315         .arg(object->metaObject()->className());
0316         return false;
0317     }
0318     if (_ids.contains(n)) {
0319         _errorMsg = QObject::tr("Non-unique ID attribute value for %1")
0320         .arg(object->metaObject()->className());
0321         return false;
0322     }
0323     
0324     _ids.insert(n, object);
0325     
0326     return parseProperties(object, element);
0327 }
0328 
0329 bool StepDomDocument::parseProperties(Object* object, const QDomElement& parent)
0330 {
0331     int properties = object->metaObject()->propertyCount();
0332     for (int n = 0; n < properties; ++n) {
0333         const MetaProperty* property = object->metaObject()->property(n);
0334         
0335         if (!property->isStored()) continue;
0336         
0337         QString name = property->name();
0338         QDomElement propertyElement = parent.firstChildElement(name);
0339         if (propertyElement.isNull()) continue;
0340         
0341         QString text = propertyElement.text();
0342         if (property->userTypeId() == qMetaTypeId<Object*>()) {
0343             int n = text.trimmed().toInt();
0344             _links.push_back(qMakePair(qMakePair(object, property), n));
0345         }
0346         else if (!property->writeString(object, text)) {
0347             _errorMsg = QObject::tr("Property \"%1\" of \"%2\" has illegal value")
0348                 .arg(name, object->metaObject()->className());
0349             return false;
0350         }
0351     }
0352     
0353     return true;
0354 }
0355 
0356 bool StepDomDocument::connectLinks()
0357 {
0358     foreach (const Link& link, _links) {
0359         QVariant target = QVariant::fromValue(_ids.value(link.second, nullptr));
0360         if (!link.first.second->writeVariant(link.first.first, target)) {
0361             _errorMsg = QObject::tr("Property \"%1\" of \"%2\" has illegal value")
0362                 .arg(link.first.second->name(), link.first.first->metaObject()->className());
0363             return false;
0364         }
0365     }
0366     
0367     return true;
0368 }
0369 } // namespace
0370 
0371 bool XmlFile::save(const World* world)
0372 {
0373     if(!_device->isOpen() || !_device-> isWritable()) {
0374         _errorString = QObject::tr("File is not writable.");
0375         return false;
0376     }
0377 
0378     StepStreamWriter writer(_device);
0379     return writer.writeWorld(world);
0380 }
0381 
0382 bool XmlFile::load(World* world, const Factory* factory)
0383 {
0384     StepDomDocument document(world, factory);
0385     if (!document.parse(_device)) {
0386         _errorString = document.errorMsg();
0387         return false;
0388     }
0389     
0390     return true;
0391 }
0392 } // namespace StepCore
0393 
0394 #endif //STEPCORE_WITH_QT
0395