File indexing completed on 2024-04-28 04:04:48

0001 /***************************************************************************
0002  *   Copyright 2007      Francesco Rossi <redsh@email.it>                  *
0003  *   Copyright 2006-2007 Mick Kappenburg <ksudoku@kappendburg.net>         *
0004  *   Copyright 2006-2007 Johannes Bergmeier <johannes.bergmeier@gmx.net>   *
0005  *   Copyright 2015      Ian Wadham <iandw.au@gmail.com>                   *
0006  *                                                                         *
0007  *   This program is free software; you can redistribute it and/or modify  *
0008  *   it under the terms of the GNU General Public License as published by  *
0009  *   the Free Software Foundation; either version 2 of the License, or     *
0010  *   (at your option) any later version.                                   *
0011  *                                                                         *
0012  *   This program is distributed in the hope that it will be useful,       *
0013  *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
0014  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
0015  *   GNU General Public License for more details.                          *
0016  *                                                                         *
0017  *   You should have received a copy of the GNU General Public License     *
0018  *   along with this program; if not, write to the                         *
0019  *   Free Software Foundation, Inc.,                                       *
0020  *   51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.         *
0021  ***************************************************************************/
0022 
0023 #include "serializer.h"
0024 #include "ksudokugame.h"
0025 #include "puzzle.h"
0026 
0027 #include <QDomDocument>
0028 #include <QFile>
0029 #include <QTextStream>
0030 #include <QTemporaryFile>
0031 
0032 #include <KIO/FileCopyJob>
0033 #include <KIO/StoredTransferJob>
0034 #include <KJobWidgets>
0035 #include <KLocalizedString>
0036 
0037 #include "ksudoku.h"
0038 #include "symbols.h"
0039 #include "settings.h"
0040 
0041 namespace ksudoku {
0042 
0043 const char *     typeNames[] = {"Plain", "XSudoku", "Jigsaw", "Aztec",
0044                 "Samurai", "TinySamurai", "Roxdoku",
0045                 "Mathdoku", "KillerSudoku"};
0046 const SudokuType types[]     = {Plain, XSudoku, Jigsaw, Aztec,
0047                 Samurai, TinySamurai, Roxdoku,
0048                 Mathdoku, KillerSudoku};
0049 
0050 Game Serializer::deserializeGame(const QDomElement &element) {
0051     bool hasPuzzle = false;
0052     Puzzle* puzzle = nullptr;
0053     bool hasHistory = false;
0054     QList<HistoryEvent> history;
0055 
0056     bool hadHelp = static_cast<bool>(element.attribute(QStringLiteral("had-help"), QStringLiteral("0")).toInt());
0057     int  msecsElapsed = element.attribute(QStringLiteral("msecs-elapsed"), QStringLiteral("0")).toInt();
0058 
0059     QDomNode child = element.firstChild();
0060     while (!child.isNull()) {
0061         if(child.isElement()) {
0062             if(child.nodeName() == QLatin1String("puzzle")) {
0063                 if(hasPuzzle) {
0064                     delete puzzle;
0065                     return Game();
0066                 }
0067 
0068                 puzzle = deserializePuzzle(child.toElement());
0069                 hasPuzzle = true;
0070             } else if(child.nodeName() == QLatin1String("history")) {
0071                 if(hasHistory) {
0072                     delete puzzle;
0073                     return Game();
0074                 }
0075 
0076                 history = deserializeHistory(child.toElement());
0077                 hasHistory = true;
0078             }
0079         }
0080         child = child.nextSibling();
0081     }
0082 
0083     if(!puzzle) return Game();
0084 
0085     Game game(puzzle);
0086     game.setUserHadHelp(hadHelp);
0087 
0088     if(hasHistory) {
0089         for(int i = 0; i < history.count(); ++i)
0090             game.doEvent(history[i]);
0091     }
0092 
0093     game.setTime(msecsElapsed);
0094     return game;
0095 }
0096 
0097 Puzzle* Serializer::deserializePuzzle(const QDomElement &element) {
0098     bool hasGraph = false;
0099     SKGraph* graph = nullptr;
0100     bool hasValues = false;
0101     QString valuesStr;
0102     bool hasSolution = false;
0103     QString solutionStr;
0104 
0105     QString content;
0106     QDomNode child = element.firstChild();
0107     while (!child.isNull()) {
0108         if(child.isElement()) {
0109             if(child.nodeName() == QLatin1String("graph")) {
0110                 if(hasGraph) {
0111                     delete graph;
0112                     return nullptr;
0113                 }
0114 
0115                 graph = deserializeGraph(child.toElement());
0116                 hasGraph = true;
0117             } else if(child.nodeName() == QLatin1String("values")) {
0118                 if(hasValues) {
0119                     delete graph;
0120                     return nullptr;
0121                 }
0122 
0123                 valuesStr = child.toElement().text();
0124                 hasValues = true;
0125             } else if(child.nodeName() == QLatin1String("solution")) {
0126                 // TODO remove deserialization of solution, it is no longer required
0127                 if(hasSolution) {
0128                     delete graph;
0129                     return nullptr;
0130                 }
0131 
0132                 solutionStr = child.toElement().text();
0133                 hasSolution = true;
0134             }
0135         }
0136         child = child.nextSibling();
0137     }
0138 
0139     if(!graph) return nullptr;
0140     if(valuesStr.length() != graph->size()) {
0141         delete graph;
0142         return nullptr;
0143     }
0144     // TODO remove deserialization of solution, it is no longer required
0145     if(solutionStr.length() != 0 && solutionStr.length() != graph->size()) {
0146         delete graph;
0147         return nullptr;
0148     }
0149 
0150     auto* puzzle = new Puzzle(graph, hasSolution);
0151 
0152     BoardContents values;
0153     values.resize(graph->size());
0154     for(int i = 0; i < graph->size(); ++i) {
0155         values[i] = Symbols::ioSymbol2Value(valuesStr[i]);
0156     }
0157 
0158     // TODO remove deserialization of solution, it is no longer required
0159     BoardContents solution;
0160     if(solutionStr.length() != 0) {
0161         solution.resize(graph->size());
0162         for(int i = 0; i < graph->size(); ++i) {
0163             solution[i] = Symbols::ioSymbol2Value(solutionStr[i]);
0164         }
0165     }
0166 
0167     puzzle->init(values);
0168     return puzzle;
0169 }
0170 
0171 static int readInt(const QDomElement &element, const QString& name, int* err)
0172 { //out of class, cannot be static
0173     *err = 1;
0174     QString Str = element.attribute(name);
0175     if(Str.isNull())
0176         return 0;
0177     bool noFailure = true;
0178     int num = Str.toInt(&noFailure, 0);
0179     if(!noFailure)
0180         return 0;
0181     *err = 0;
0182     return num;
0183 }
0184 
0185 SKGraph* Serializer::deserializeGraph(const QDomElement &element) {
0186     bool noFailure = true;
0187 
0188     QString orderStr = element.attribute(QStringLiteral("order"));
0189     if(orderStr.isNull())
0190         return nullptr;
0191     // Allow symbolic values for Mathdoku, set from user-config dialog.
0192     int order = (orderStr == QStringLiteral("Mathdoku")) ?
0193                      Settings::mathdokuSize() :
0194                      orderStr.toInt(&noFailure, 0);
0195     if(!noFailure)
0196         return nullptr;
0197 
0198     QString type = element.attribute(QStringLiteral("type"));
0199     if(type.isNull())
0200         return nullptr;
0201 
0202     if(type == QLatin1String("sudoku")) {
0203         auto *graph = new SKGraph(order, TypeSudoku);
0204         graph->initSudoku();
0205         return graph;
0206     } else if(type == QLatin1String("roxdoku")) {
0207         auto *graph = new SKGraph(order, TypeRoxdoku);
0208         graph->initRoxdoku();
0209         return graph;
0210     } else if(type == QLatin1String("custom")) {
0211         int err=0;
0212         int sizeX;
0213         int sizeY;
0214         int sizeZ;
0215         if (orderStr != QStringLiteral("Mathdoku")) {
0216             sizeX = readInt(element,QStringLiteral("sizeX"),&err);
0217             sizeY = readInt(element,QStringLiteral("sizeY"),&err);
0218         }
0219         else {
0220             // In Mathdoku, there are row and column groups only.
0221             sizeX = order;
0222             sizeY = order;
0223         }
0224         sizeZ = readInt(element,QStringLiteral("sizeZ"),&err);
0225 
0226         QString name = element.attribute(QStringLiteral("name"));
0227         QString typeName = element.attribute(QStringLiteral("specific-type"));
0228         SudokuType puzzleType = Plain; // Default puzzle-type.
0229             for (int n = 0; n < EndSudokuTypes; n++) {
0230             QString lookup = QLatin1String(typeNames [n]);
0231                 if (QString::compare (typeName, lookup, Qt::CaseInsensitive)
0232             == 0) {
0233                 puzzleType = types [n];
0234                     break;
0235                 }
0236         }
0237 
0238         if(err==1) return nullptr;
0239         if(sizeX<1 || sizeY<1 || sizeZ<1) return nullptr;
0240 
0241         auto* graph = new SKGraph(order, TypeCustom);
0242         graph->initCustom(name, puzzleType, order,
0243                 sizeX, sizeY, sizeZ);
0244 
0245         QDomNode child = element.firstChild();
0246         while (!child.isNull()) {
0247             if(child.isElement()) {
0248                 QDomElement e   = child.toElement();
0249                 QString     tag = e.tagName();
0250                 if (tag == QLatin1String("clique")) {
0251                 QString sz = e.attribute(QStringLiteral("size"));
0252                 if(! deserializeClique(graph, sz, e.text())) {
0253                     delete graph;   // Error return.
0254                     return nullptr;
0255                 }
0256                 }
0257                 else if (tag == QLatin1String("sudokugroups")) {
0258                 graph->initSudokuGroups(
0259                     e.attribute(QStringLiteral("at"), QStringLiteral("0")).toInt(),
0260                     (e.attribute(QStringLiteral("withblocks"),QStringLiteral("1")) == QLatin1String("1")));
0261                 }
0262                 else if (tag == QLatin1String("roxdokugroups")) {
0263                 graph->initRoxdokuGroups(
0264                     e.attribute(QStringLiteral("at"), QStringLiteral("0")).toInt());
0265                 }
0266                 else if (tag == QLatin1String("cage")) {
0267                 if(! deserializeCage(graph, e)) {
0268                     delete graph;   // Error return.
0269                     return nullptr;
0270                 }
0271                 }
0272             }
0273             child = child.nextSibling();
0274         }
0275         graph->endCustom(); // Finalise the structure of the graph.
0276         return graph;
0277     }
0278     return nullptr;
0279 }
0280 
0281 bool Serializer::deserializeClique(SKGraph * graph, const QString & size,
0282                             const QString & text) {
0283     // A group (or clique) should have a size followed by that number of
0284     // indices of cells that are members of the Group.  Normally size is
0285     // equal to m_order (e.g. 4, 9, 16, 25).
0286 
0287     int cellCount = 0;
0288     if(! size.isNull()) {
0289     cellCount = size.toInt();
0290     }
0291     if (cellCount <= 0) {
0292     return false;
0293     }
0294 
0295     const QStringList  splitData = text.split(QStringLiteral(" "), Qt::SkipEmptyParts);
0296     QList<int> data;
0297     data.clear();
0298     for (const QString &s : splitData) {
0299     --cellCount;
0300     data << s.toInt();
0301     if(cellCount <= 0) {
0302         break;
0303     }
0304     }
0305     graph->addCliqueStructure(data);
0306     return true;
0307 }
0308 
0309 bool Serializer::deserializeCage(SKGraph * graph, const QDomElement & e) {
0310     QString sizeStr = e.attribute(QStringLiteral("size"));
0311     QString text    = e.text();
0312     CageOperator op = (CageOperator) (e.attribute(QStringLiteral("operator")).toInt());
0313     int target      = e.attribute(QStringLiteral("value")).toInt();
0314     int size        = 0;
0315     QList<int> cage;
0316     if(! sizeStr.isNull()) {
0317     size = sizeStr.toInt();
0318     }
0319     if (size <= 0) {
0320     return false;
0321     }
0322 
0323     const QStringList cells = text.split(QStringLiteral(" "), Qt::SkipEmptyParts);
0324     cage.clear();
0325     for (const QString& s : cells) {
0326     cage << s.toInt();
0327     size--;
0328     if (size <= 0) {
0329         break;
0330     }
0331     }
0332 
0333     graph->addCage(cage, op, target);
0334     return true;
0335 }
0336 
0337 QList<HistoryEvent> Serializer::deserializeHistory(const QDomElement &element) {
0338     QList<HistoryEvent> history;
0339 
0340     QDomNode child = element.firstChild();
0341     while (!child.isNull()) {
0342         if(child.isElement()) {
0343             if(child.nodeName() == QLatin1String("simple-event")) {
0344                 history.append(deserializeSimpleHistoryEvent(child.toElement()));
0345             } else if(child.nodeName() == QLatin1String("complex-event")) {
0346                 history.append(deserializeComplexHistoryEvent(child.toElement()));
0347             }
0348         }
0349         child = child.nextSibling();
0350     }
0351     return history;
0352 }
0353 
0354 HistoryEvent Serializer::deserializeSimpleHistoryEvent(const QDomElement &element) {
0355     QString indexStr = element.attribute(QStringLiteral("index"));
0356     QString markerStr = element.attribute(QStringLiteral("markers"));
0357     QString valueStr = element.attribute(QStringLiteral("value"));
0358     bool given = element.attribute(QStringLiteral("given")) == QLatin1String("true");
0359     bool noFailure = true;
0360 
0361     int index = indexStr.toInt(&noFailure, 0);
0362     if(!noFailure)
0363         return HistoryEvent();
0364 
0365 
0366     if(markerStr.isNull() == valueStr.isNull())
0367         return HistoryEvent();
0368 
0369 
0370     if(!markerStr.isNull()) {
0371         QBitArray markers(markerStr.length());
0372         for(int i = 0; i < markerStr.length(); ++i)
0373             markers[i] = markerStr[i] != QLatin1Char('0');
0374 
0375         return HistoryEvent(index, CellInfo(markers));
0376     } else {
0377         int value = valueStr.toInt(&noFailure, 0);
0378         if(!noFailure)
0379             return HistoryEvent();
0380 
0381         if(given) {
0382             return HistoryEvent(index, CellInfo(GivenValue, value));
0383         } else {
0384             return HistoryEvent(index, CellInfo(CorrectValue, value));
0385         }
0386     }
0387 
0388     return HistoryEvent();
0389 }
0390 
0391 HistoryEvent Serializer::deserializeComplexHistoryEvent(const QDomElement /*element*/&) {
0392     // TODO implement this
0393     return HistoryEvent();
0394 }
0395 
0396 SKGraph *Serializer::loadCustomShape(const QUrl& url, QWidget* window, QString& errorMsg) {
0397     Q_UNUSED(window);
0398     if ( url.isEmpty() ) {
0399         errorMsg = i18n("Unable to download file: URL is empty.");
0400         return nullptr;
0401     }
0402     QDomDocument doc;
0403     QFile file(url.toLocalFile());
0404 
0405     if ( !file.open(QIODevice::ReadOnly) ) {
0406         errorMsg = i18n("Unable to open file.");
0407         return nullptr;
0408     }
0409 
0410     const QDomDocument::ParseResult parseResult = doc.setContent(&file);
0411     if (!parseResult) {
0412         qDebug() << "Error " << parseResult.errorMessage << " from line " << parseResult.errorLine << ":" << parseResult.errorColumn << " from file " << url.toString();
0413         errorMsg = i18n("Cannot read XML file on line %1", parseResult.errorLine);
0414 
0415         return nullptr;
0416     }
0417 
0418     QDomNode child = doc.documentElement().firstChild();
0419     while (!child.isNull()) {
0420         if(child.isElement()) {
0421             if(child.nodeName() == QLatin1String("graph")) {
0422                 return deserializeGraph(child.toElement());
0423             }
0424         }
0425         child = child.nextSibling();
0426     }
0427 
0428     return nullptr;
0429 }
0430 
0431 Game Serializer::load(const QUrl& url, QWidget* window, QString& errorMsg) {
0432     if ( url.isEmpty() ) return Game();
0433     QDomDocument doc;
0434 
0435     KIO::StoredTransferJob *downloadJob = KIO::storedGet(url);
0436     KJobWidgets::setWindow(downloadJob, window);
0437     downloadJob->exec();
0438 
0439     if( downloadJob->error() ) {
0440         errorMsg = i18n("Unable to download file.");
0441         return Game();
0442     }
0443 
0444     const QDomDocument::ParseResult parseResult = doc.setContent(downloadJob->data());
0445     if (!parseResult) {
0446         errorMsg = i18n("Cannot read XML file on line %1", parseResult.errorLine);
0447         return Game();
0448     }
0449 
0450     // used to ensure, that there is only one game
0451     bool hasGame = false;
0452     Game game;
0453 
0454     QDomNode child = doc.documentElement().firstChild();
0455     while (!child.isNull()) {
0456         if(child.isElement()) {
0457             if(child.nodeName() == QLatin1String("game")) {
0458                 if(hasGame)
0459                     return Game();
0460 
0461                 game = deserializeGame(child.toElement());
0462                 hasGame = true;
0463             }
0464         }
0465         child = child.nextSibling();
0466     }
0467 
0468     return game;
0469 }
0470 
0471 bool Serializer::serializeGame(QDomElement& parent, const Game& game) {
0472     QDomElement element = parent.ownerDocument().createElement(QStringLiteral("game"));
0473     element.setAttribute(QStringLiteral("had-help"), game.userHadHelp());
0474     element.setAttribute(QStringLiteral("msecs-elapsed"), game.msecsElapsed());
0475     serializePuzzle(element, game.puzzle());
0476     serializeHistory(element, game);
0477     parent.appendChild(element);
0478     return true;
0479 }
0480 
0481 bool Serializer::serializePuzzle(QDomElement& parent, const Puzzle* puzzle) {
0482     QString contentStr;
0483 
0484     QDomDocument doc = parent.ownerDocument();
0485     QDomElement element = doc.createElement(QStringLiteral("puzzle"));
0486     serializeGraph(element, puzzle->graph());
0487 
0488     for(int i = 0; i < puzzle->size(); ++i) {
0489         contentStr += Symbols::ioValue2Symbol(puzzle->value(i));
0490     }
0491 
0492     QDomElement content = doc.createElement(QStringLiteral("values"));
0493     content.appendChild(doc.createTextNode(contentStr));
0494     element.appendChild(content);
0495 
0496     if(puzzle->hasSolution()) {
0497         contentStr = QString();
0498         for(int i = 0; i < puzzle->size(); ++i) {
0499             contentStr += Symbols::ioValue2Symbol(puzzle->solution(i));
0500         }
0501         content = doc.createElement(QStringLiteral("solution"));
0502         content.appendChild(doc.createTextNode(contentStr));
0503         element.appendChild(content);
0504     }
0505 
0506     parent.appendChild(element);
0507     return true;
0508 }
0509 
0510 bool Serializer::serializeGraph(QDomElement &parent, const SKGraph *graph)
0511 {
0512     QDomElement element = parent.ownerDocument().createElement(QStringLiteral("graph"));
0513     element.setAttribute(QStringLiteral("order"), graph->order());
0514 
0515     GameType type = graph->type();
0516     element.setAttribute(QStringLiteral("type") , (type == TypeSudoku) ? QStringLiteral("sudoku") :
0517                                                      (type == TypeRoxdoku) ? QStringLiteral("roxdoku") : QStringLiteral("custom"));
0518 
0519     int n = -1;
0520     SudokuType puzzleType = graph->specificType();
0521     for (n = 0; n < EndSudokuTypes; n++) {
0522         if (puzzleType == types [n]) {
0523         break;
0524         }
0525     }
0526     element.setAttribute(QStringLiteral("specific-type"), (n < 0) ? QStringLiteral("Plain") : QLatin1String(typeNames[n]));
0527 
0528     if(type == TypeCustom) {
0529         element.setAttribute(QStringLiteral("name"), graph->name());
0530         element.setAttribute(QStringLiteral("sizeX"), graph->sizeX());
0531         element.setAttribute(QStringLiteral("sizeY"), graph->sizeY());
0532         element.setAttribute(QStringLiteral("sizeZ"), graph->sizeZ());
0533 
0534         for (int n = 0; n < graph->structureCount(); n++) {
0535         QDomElement e;
0536         SKGraph::StructureType sType = graph->structureType(n);
0537         switch (sType) {
0538         case SKGraph::SudokuGroups:
0539             e = parent.ownerDocument().createElement(QStringLiteral("sudokugroups"));
0540             e.setAttribute(QStringLiteral("at"), graph->structurePosition(n));
0541             e.setAttribute(QStringLiteral("withblocks"),
0542                            graph->structureHasBlocks(n) ? QStringLiteral("1") : QStringLiteral("0"));
0543             break;
0544         case SKGraph::RoxdokuGroups:
0545             e = parent.ownerDocument().createElement(QStringLiteral("roxdokugroups"));
0546             e.setAttribute(QStringLiteral("at"), graph->structurePosition(n));
0547             break;
0548         case SKGraph::Clique:
0549             e = parent.ownerDocument().createElement(QStringLiteral("clique"));
0550             int cNum  = graph->structurePosition(n);
0551             int cSize = graph->clique(cNum).size();
0552             e.setAttribute(QStringLiteral("size"), cSize);
0553 
0554             // Serialize the cell-numbers in the clique (or group).
0555             QString contentStr = QLatin1String("");
0556             for(int j=0; j < cSize; j++) {
0557             contentStr += QString::number
0558                               (graph->clique(cNum).at(j)) + QLatin1Char(' ');
0559             }
0560             e.appendChild(parent.ownerDocument().
0561                     createTextNode(contentStr));
0562             break;
0563         }
0564         element.appendChild(e);
0565         }
0566 
0567         // Add cages if this is a Mathdoku or Killer Sudoku puzzle.
0568         for (int n = 0; n < graph->cageCount(); n++) {
0569         QDomElement e = parent.ownerDocument().createElement(QStringLiteral("cage"));
0570         const QList<int> cage = graph->cage(n);
0571         e.setAttribute(QStringLiteral("operator"), graph->cageOperator(n));
0572         e.setAttribute(QStringLiteral("value"), graph->cageValue(n));
0573         e.setAttribute(QStringLiteral("size"), cage.size());
0574 
0575         // Serialize the cell-numbers in the cage.
0576         QString contentStr = QStringLiteral(" ");
0577         for (const int cell : cage) {
0578             contentStr += QString::number(cell) + QLatin1Char(' ');
0579         }
0580         e.appendChild(parent.ownerDocument().
0581                     createTextNode(contentStr));
0582         element.appendChild(e);
0583         }
0584     }
0585 
0586     parent.appendChild(element);
0587     return true;
0588 }
0589 
0590 bool Serializer::serializeHistory(QDomElement& parent, const Game& game) {
0591     QDomElement element = parent.ownerDocument().createElement(QStringLiteral("history"));
0592 
0593     for(int i = 0; i < game.historyLength(); ++i) {
0594         if(!serializeHistoryEvent(element, game.historyEvent(i)))
0595             return false;
0596     }
0597 
0598     parent.appendChild(element);
0599     return true;
0600 }
0601 
0602 bool Serializer::serializeHistoryEvent(QDomElement& parent, const HistoryEvent& event) {
0603     QDomElement element;
0604 
0605     const QList<int>& indices = event.cellIndices();
0606     const QList<CellInfo>& changes = event.cellChanges();
0607 
0608     if(indices.count() == 0) {
0609         return true;
0610     } else if(indices.count() == 1) {
0611         element = parent.ownerDocument().createElement(QStringLiteral("simple-event"));
0612 
0613         element.setAttribute(QStringLiteral("index"), indices[0]);
0614         switch(changes[0].state()) {
0615             case GivenValue:
0616                 element.setAttribute(QStringLiteral("given"), QStringLiteral("true"));
0617                 element.setAttribute(QStringLiteral("value"), changes[0].value());
0618                 break;
0619             case ObviouslyWrong:
0620             case WrongValue:
0621             case CorrectValue:
0622                 element.setAttribute(QStringLiteral("value"), changes[0].value());
0623                 break;
0624             case Marker: {
0625                 QString str;
0626 
0627                 QBitArray markers = changes[0].markers();
0628                 for(int j = 0; j < markers.size(); ++j) {
0629                     str += markers[j] ? QLatin1Char('1') : QLatin1Char('0');
0630                 }
0631 
0632                 element.setAttribute(QStringLiteral("markers"), str);
0633             } break;
0634         }
0635     } else {
0636         element = parent.ownerDocument().createElement(QStringLiteral("complex-event"));
0637         for(int i = 0; i < indices.count(); ++i) {
0638             QDomElement subElement = parent.ownerDocument().createElement(QStringLiteral("simple-event"));
0639 
0640             subElement.setAttribute(QStringLiteral("index"), indices[i]);
0641             switch(changes[i].state()) {
0642                 case GivenValue:
0643                     subElement.setAttribute(QStringLiteral("given"), QStringLiteral("true"));
0644                     subElement.setAttribute(QStringLiteral("value"), changes[i].value());
0645                     break;
0646                 case ObviouslyWrong:
0647                 case WrongValue:
0648                 case CorrectValue:
0649                     subElement.setAttribute(QStringLiteral("value"), changes[i].value());
0650                     break;
0651                 case Marker: {
0652                     QString str;
0653 
0654                     QBitArray markers = changes[i].markers();
0655                     for(int j = 0; j < markers.size(); ++j) {
0656                         str += markers[i] ? QLatin1Char('1') : QLatin1Char('0');
0657                     }
0658 
0659                     subElement.setAttribute(QStringLiteral("markers"), str);
0660                 } break;
0661             }
0662 
0663             element.appendChild(subElement);
0664         }
0665     }
0666 
0667     parent.appendChild(element);
0668     return true;
0669 }
0670 
0671 bool Serializer::store(const Game& game, const QUrl& url, QWidget* window, QString& errorMsg) {
0672     QDomDocument doc( QStringLiteral("ksudoku") );
0673     QDomElement root = doc.createElement( QStringLiteral("ksudoku") );
0674     doc.appendChild( root );
0675 
0676     serializeGame(root, game);
0677 
0678     QTemporaryFile file;
0679     if ( !file.open() ) {
0680         errorMsg = i18n("Unable to create temporary file.");
0681         return false;
0682     }
0683 
0684     QTextStream stream(&file);
0685     stream << doc.toString();
0686     stream.flush();
0687 
0688     KIO::FileCopyJob *copyJob = KIO::file_copy(QUrl::fromLocalFile(file.fileName()), url, -1, KIO::Overwrite);
0689     KJobWidgets::setWindow(copyJob , window);
0690     copyJob->exec();
0691     if(copyJob->error())
0692     {
0693         errorMsg = i18n("Unable to upload file.");
0694         return false;
0695     }
0696     return true;
0697 }
0698 
0699 }