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