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