]> git.sven.stormbind.net Git - sven/vym.git/blobdiff - src/xml-vym.cpp
New upstream version 2.9.22
[sven/vym.git] / src / xml-vym.cpp
diff --git a/src/xml-vym.cpp b/src/xml-vym.cpp
new file mode 100644 (file)
index 0000000..d4f9228
--- /dev/null
@@ -0,0 +1,1147 @@
+#include "xml-vym.h"
+
+#include <QColor>
+#include <QMessageBox>
+#include <QTextStream>
+#include <typeinfo>
+
+#include "attributeitem.h"
+#include "branchitem.h"
+#include "flag.h"
+#include "linkablemapobj.h"
+#include "mainwindow.h"
+#include "misc.h"
+#include "settings.h"
+#include "slideitem.h"
+#include "task.h"
+#include "taskmodel.h"
+#include "xlinkitem.h"
+
+extern Main *mainWindow;
+extern Settings settings;
+extern TaskModel *taskModel;
+extern QString vymVersion;
+
+parseVYMHandler::parseVYMHandler()
+{
+    // Default is to load everything
+    contentFilter = 0x0000; // TODO  use filters for all content types below
+}
+
+void parseVYMHandler::setContentFilter(const int &c) { contentFilter = c; }
+
+bool parseVYMHandler::startDocument()
+{
+    errorProt = "";
+    state = StateInit;
+    stateStack.clear();
+    stateStack.append(StateInit);
+    htmldata = "";
+    isVymPart = false;
+    useProgress = false;
+    return true;
+}
+
+bool parseVYMHandler::startElement(const QString &, const QString &,
+                                   const QString &eName,
+                                   const QXmlAttributes &atts)
+{
+    QColor col;
+    /* Testing
+    qDebug() << "startElement: <" << eName
+             << ">     state=" << state
+             << "  laststate=" << stateStack.last()
+             << "   loadMode=" << loadMode
+            //<<"       line=" << QXmlDefaultHandler::lineNumber();
+             << "contentFilter=" << contentFilter;
+    */
+
+    stateStack.append(state);
+    if (state == StateInit && (eName == "vymmap")) {
+        state = StateMap;
+        branchesTotal = 0;
+        branchesCounter = 0;
+
+        if (loadMode == NewMap || loadMode == DefaultMap) {
+            // Create mapCenter
+            model->clear();
+            lastBranch = NULL;
+
+            readMapAttr(atts);
+        }
+        // Check version
+        if (!atts.value("version").isEmpty()) {
+            version = atts.value("version");
+            if (!versionLowerOrEqualThanVym(version)) {
+                QMessageBox::warning(
+                    0, QObject::tr("Warning: Version Problem"),
+                    QObject::tr(
+                        "<h3>Map is newer than VYM</h3>"
+                        "<p>The map you are just trying to load was "
+                        "saved using vym %1. "
+                        "The version of this vym is %2. "
+                        "If you run into problems after pressing "
+                        "the ok-button below, updating vym should help.</p>")
+                        .arg(version)
+                        .arg(vymVersion) +
+                    QObject::tr(
+                        "<p>The map will be opened readonly, because not "
+                        "all information from new maps can be saved with this "
+                        "version of vym. Please be careful!"));
+                model->setReadOnly(true);
+            }
+            else
+                model->setVersion(version);
+        }
+    }
+    else if (eName == "mapdesign" && state == StateMap) {
+        state = StateMapDesign;
+    }
+    else if (eName == "md" && state == StateMapDesign) {
+        state = StateMD;
+        readMapDesignCompatibleAttr(atts);
+    }
+    else if (eName == "select" && state == StateMap) {
+        state = StateMapSelect;
+    }
+    else if (eName == "setting" && state == StateMap) {
+        state = StateMapSetting;
+        if (loadMode == NewMap) {
+            htmldata.clear();
+            readSettingAttr(atts);
+        }
+    }
+    else if (eName == "slide" && state == StateMap) {
+        state = StateMapSlide;
+        if (!(contentFilter & SlideContent)) {
+            // Ignore slides during paste
+            lastSlide = model->addSlide();
+            if (insertPos >= 0)
+                model->relinkSlide(lastSlide, insertPos);
+
+            readSlideAttr(atts);
+        }
+    }
+    else if (eName == "mapcenter" && state == StateMap) {
+        state = StateMapCenter;
+        if (loadMode == NewMap) {
+            // Really use the found mapcenter as MCO in a new map
+            lastBranch = model->createMapCenter();
+        }
+        else {
+            // Treat the found mapcenter as a branch
+            // in an existing map
+            BranchItem *bi = model->getSelectedBranch();
+            if (bi) {
+                lastBranch = bi;
+                if (loadMode == ImportAdd) {
+                    // Import Add
+                    if (insertPos < 0)
+                        lastBranch = model->createBranch(lastBranch);
+                    else {
+                        lastBranch = model->addNewBranch(lastBranch, insertPos);
+                        insertPos++;
+                    }
+                }
+                else {
+                    // Import Replace
+                    if (insertPos < 0) {
+                        insertPos = lastBranch->num() + 1;
+                        model->clearItem(lastBranch);
+                    }
+                    else {
+                        BranchItem *pi = bi->parentBranch();
+                        lastBranch = model->addNewBranch(pi, insertPos);
+                        insertPos++;
+                    }
+                }
+            }
+            else
+                // if nothing selected, add mapCenter without parent
+                lastBranch = model->createMapCenter();
+        }
+        readBranchAttr(atts);
+    }
+    else if ((eName == "standardflag" || eName == "standardFlag") &&
+             (state == StateMapCenter || state == StateBranch)) {
+        state = StateStandardFlag;
+    }
+    else if (eName == "userflagdef" && state == StateMap) {
+        state = StateUserFlagDef;
+        return (readUserFlagDefAttr(atts));
+    }
+    else if (eName == "userflag" &&
+             (state == StateMapCenter || state == StateBranch)) {
+        state = StateUserFlag;
+        return (readUserFlagAttr(atts));
+    }
+    else if (eName == "heading" &&
+             (state == StateMapCenter || state == StateBranch ||
+              state == StateInit)) {
+        if (state == StateInit) {
+            // Only read some stuff like VymNote or Heading
+            // e.g. for undo/redo
+            lastBranch = model->getSelectedBranch();
+            if (version.isEmpty())
+                version = "0.0.0";
+        }
+        if (!lastBranch)
+            return false;
+
+        state = StateHeading;
+        htmldata.clear();
+        vymtext.clear();
+        if (!atts.value("fonthint").isEmpty())
+            vymtext.setFontHint(atts.value("fonthint"));
+        if (!atts.value("textMode").isEmpty()) {
+            if (atts.value("textMode") == "richText")
+                vymtext.setRichText(true);
+            else
+                vymtext.setRichText(false);
+        }
+        if (!atts.value("textColor").isEmpty()) {
+            // For compatibility with <= 2.4.0 set both branch and
+            // heading color
+            col.setNamedColor(atts.value("textColor"));
+            lastBranch->setHeadingColor(col);
+            vymtext.setColor(col);
+        }
+        if (!atts.value("text").isEmpty())
+            vymtext.setText(unquoteQuotes(atts.value("text")));
+    }
+    else if (eName == "task" &&
+             (state == StateMapCenter || state == StateBranch)) {
+        state = StateTask;
+        lastTask = taskModel->createTask(lastBranch);
+        if (!readTaskAttr(atts))
+            return false;
+    }
+    else if (eName == "note" &&
+             (state == StateMapCenter ||
+              state == StateBranch)) { // only for backward compatibility
+                                       // (<1.4.6). Use htmlnote now.
+        state = StateNote;
+        htmldata.clear();
+        vymtext.clear();
+        if (!readNoteAttr(atts))
+            return false;
+    }
+    else if (eName == "htmlnote" &&
+             state == StateMapCenter) { // only for backward compatibility. Use
+                                        // vymnote now
+        state = StateHtmlNote;
+        vymtext.clear();
+        if (!atts.value("fonthint").isEmpty())
+            vymtext.setFontHint(atts.value("fonthint"));
+    }
+    else if (eName == "vymnote" &&
+             (state == StateMapCenter || state == StateBranch ||
+              state == StateInit)) {
+        if (state == StateInit)
+        // Only read some stuff like VymNote or Heading
+        // e.g. for undo/redo
+        {
+            lastBranch = model->getSelectedBranch();
+            if (version.isEmpty())
+                version = "0.0.0";
+        }
+        state = StateVymNote;
+        htmldata.clear();
+        vymtext.clear();
+        if (!atts.value("fonthint").isEmpty())
+            vymtext.setFontHint(atts.value("fonthint"));
+        if (!atts.value("textMode").isEmpty()) {
+            if (atts.value("textMode") == "richText")
+                vymtext.setRichText(true);
+            else
+                vymtext.setRichText(false);
+        }
+        if (!atts.value("text").isEmpty())
+            vymtext.setText(unquoteQuotes(atts.value("text")));
+    }
+    else if (eName == "floatimage" &&
+             (state == StateMapCenter || state == StateBranch)) {
+        state = StateImage;
+        lastImage = model->createImage(lastBranch);
+        if (!readImageAttr(atts))
+            return false;
+    }
+    else if ((eName == "branch" || eName == "floatimage") &&
+             state == StateMap) {
+        // This is used in vymparts, which have no mapcenter or for undo
+        isVymPart = true;
+        TreeItem *ti = model->getSelectedItem();
+        if (!ti) {
+            // If a vym part is _loaded_ (not imported),
+            // selection==lmo==NULL
+            // Treat it like ImportAdd then...
+            loadMode = ImportAdd;
+            // we really have no MCO at this time
+            lastBranch = model->createMapCenter();
+            model->select(lastBranch);
+            model->setHeadingPlainText("Import");
+            ti = lastBranch;
+        }
+        if (ti && ti->isBranchLikeType()) {
+            lastBranch = (BranchItem *)ti;
+            if (eName == "branch") {
+                state = StateBranch;
+                if (loadMode == ImportAdd) {
+                    lastBranch = model->createBranch(lastBranch);
+                    model->setLatestAddedItem(lastBranch);
+                    if (insertPos >= 0)
+                        model->relinkBranch(lastBranch, (BranchItem *)ti,
+                                            insertPos);
+                }
+                else
+                    model->clearItem(lastBranch);
+                readBranchAttr(atts);
+            }
+            else if (eName == "floatimage") {
+                state = StateImage;
+                lastImage = model->createImage(lastBranch);
+                model->setLatestAddedItem(lastImage);
+                if (!readImageAttr(atts))
+                    return false;
+            }
+            else
+                return false;
+        }
+        else
+            return false;
+    }
+    else if (eName == "branch" && state == StateMapCenter) {
+        state = StateBranch;
+        lastBranch = model->createBranch(lastBranch);
+        readBranchAttr(atts);
+    }
+    else if (eName == "htmlnote" &&
+             state == StateBranch) { // only for backward compatibility. Use
+                                     // vymnote now
+        state = StateHtmlNote;
+        vymtext.clear();
+        if (!atts.value("fonthint").isEmpty())
+            vymtext.setFontHint(atts.value("fonthint"));
+    }
+    else if (eName == "frame" &&
+             (state == StateBranch || state == StateMapCenter)) {
+        state = StateFrame;
+        if (!readFrameAttr(atts))
+            return false;
+    }
+    else if (eName == "xlink" && state == StateBranch) {
+        // Obsolete after 1.13.2
+        state = StateBranchXLink;
+        if (!readXLinkAttr(atts))
+            return false;
+    }
+    else if (eName == "xlink" && state == StateMap) {
+        state = StateLink;
+        if (!readLinkNewAttr(atts))
+            return false;
+    }
+    else if (eName == "branch" && state == StateBranch) {
+        lastBranch = model->createBranch(lastBranch);
+        readBranchAttr(atts);
+    }
+    else if (eName == "html" &&
+             (state == StateHtmlNote ||
+              state == StateNote ||
+              state == StateVymNote)) { // Only for backward compatibility
+        state = StateHtml;
+        htmldata = "<" + eName;
+        readHtmlAttr(atts);
+        htmldata += ">";
+    }
+    else if (eName == "attribute" &&
+             (state == StateBranch || state == StateMapCenter)) {
+        state = StateAttribute;
+        AttributeItem *ai = new AttributeItem(lastBranch);
+        if (ai) {
+            if (!atts.value("key").isEmpty())
+                ai->setKey(atts.value("key"));
+
+            QString type = atts.value("type");
+            QString val = atts.value("value");
+            if (!type.isEmpty()) {
+                if (type == "Integer")
+                    ai->setValue(val.toInt());
+                else if (type == "String")
+                    ai->setValue(val);
+                else if (type == "Undefined") {
+                    ai->setValue(val);
+                    ai->setAttributeType(AttributeItem::Undefined);
+                    qWarning() << "Found attribute type 'Undefined'";
+                } else if (type == "DateTime") {
+                    ai->setValue(QDateTime::fromString(val, Qt::ISODate));
+                } else
+                    qWarning() << "Found unknown attribute type: " << type;
+            } else {
+                if (!atts.value("value").isEmpty())
+                    ai->setValue(atts.value("value"));
+            }
+        }
+        model->setAttribute(lastBranch, ai);
+    }
+    else if (state == StateHtml) {
+        // Only for backward compatibility
+        // accept all while in html mode,
+        htmldata += "<" + eName;
+        readHtmlAttr(atts);
+        htmldata += ">";
+    }
+    else
+        return false; // Error
+    return true;
+}
+
+bool parseVYMHandler::endElement(const QString &, const QString &,
+                                 const QString &eName)
+{
+    /* Testing
+    QString h;
+    lastBranch ? h = lastBranch->getHeadingPlain() : h = "";
+    qDebug() << "endElement </" << eName << ">  state=" << state << " lastBranch=" << h;
+    */
+
+    switch (state) {
+    case StateMap:
+    case StateMapDesign:
+    case StateMD:
+        break;
+    case StateMapCenter:
+        model->emitDataChanged(lastBranch);
+        lastBranch = (BranchItem *)(lastBranch->parent());
+        break;
+    case StateBranch:
+        // Empty branches may not be scrolled
+        // (happens if bookmarks are imported)
+        if (lastBranch->isScrolled() && lastBranch->branchCount() == 0)
+            lastBranch->unScroll();
+
+        model->emitDataChanged(lastBranch);
+        lastBranch = (BranchItem *)(lastBranch->parent());
+        lastBranch->setLastSelectedBranch(0);
+        break;
+    case StateTask:
+        break;
+    case StateHeading:
+        if (versionLowerOrEqual(version, "2.4.99") &&
+            htmldata.contains("<html>"))
+            // versions before 2.5.0 didn't use CDATA to save richtext
+            vymtext.setAutoText(htmldata);
+        else {
+            // Versions 2.5.0 to 2.7.562  had HTML data encoded as CDATA
+            // Later versions use the <vymnote  text="...">  attribute,
+            // which is set already in begin element
+            // If both htmldata and vymtext are already available, use the
+            // vymtext
+            if (vymtext.isEmpty())
+                vymtext.setText(htmldata);
+        }
+        lastBranch->setHeading(vymtext);
+        break;
+    case StateHtmlNote: // Richtext note, needed anyway for backward
+                        // compatibility
+        if (htmldata.contains("<html"))
+            vymtext.setRichText(htmldata);
+        else
+            vymtext.setPlainText(htmldata);
+        lastBranch->setNote(vymtext);
+        break;
+    case StateMapSlide:
+        lastSlide = NULL;
+        break;
+    case StateNote:
+        // version < 1.4.6
+        if (!htmldata.isEmpty()) {
+            if (htmldata.contains("<html"))
+                vymtext.setRichText(htmldata);
+            else
+                vymtext.setPlainText(htmldata);
+        }
+        lastBranch->setNote(vymtext);
+        break;
+    case StateMapSetting:
+        // version >= 2.5.0  previously value only as attribut
+        settings.setLocalValue(model->getDestPath(), lastSetting, htmldata);
+        break;
+    case StateVymNote: // Might be richtext or plaintext with
+        // version >= 1.13.8
+        if (versionLowerOrEqual(version, "2.4.99") &&
+            htmldata.contains("<html>"))
+            // versions before 2.5.0 didn't use CDATA to save richtext
+            vymtext.setAutoText(htmldata);
+        else {
+            // Versions 2.5.0 to 2.7.562  had HTML data encoded as CDATA
+            // Later versions use the <vymnote  text="...">  attribute,
+            // which is set already in begin element
+            // If both htmldata and vymtext are already available, use the
+            // vymtext
+            if (vymtext.isEmpty())
+                vymtext.setText(htmldata);
+        }
+        lastBranch->setNote(vymtext);
+        break;
+    case StateHtml:
+        htmldata += "</" + eName + ">";
+        if (eName == "html")
+            htmldata.replace("<br></br>", "<br />");
+        break;
+    default:
+        break;
+    }
+    state = stateStack.takeLast();
+    return true;
+}
+
+bool parseVYMHandler::characters(const QString &ch)
+{
+    // qDebug()<< "xml-vym: characters " << ch << "  state=" << state;
+
+    QString ch_org = quoteMeta(ch);
+    QString ch_simplified = ch.simplified();
+
+    switch (state) {
+    case StateInit:
+    case StateMap:
+    case StateMapDesign:
+    case StateMD:
+        break;
+    case StateMapSelect:
+        model->select(ch_simplified);
+        break;
+    case StateMapSetting:
+        htmldata += ch;
+        break;
+    case StateMapCenter:
+        break;
+    case StateNote: // only in vym <1.4.6
+        htmldata += ch_simplified;
+        break;
+    case StateBranch:
+        break;
+    case StateStandardFlag:
+        lastBranch->activateStandardFlagByName(ch_simplified);
+        break;
+    case StateImage:
+        break;
+    case StateVymNote:
+        htmldata += ch;
+        break;
+    case StateHtmlNote: // Only for compatibility
+        htmldata += ch;
+        break;
+    case StateHtml:
+        htmldata += ch_simplified;
+        break;
+    case StateHeading:
+        htmldata += ch;
+        break;
+    default:
+        return false;
+    }
+    return true;
+}
+
+QString parseVYMHandler::errorString()
+{
+    return "the document is not in the VYM file format";
+}
+
+bool parseVYMHandler::readMapAttr( const QXmlAttributes &a)
+{
+    if (!a.value("author").isEmpty())
+        model->setAuthor(a.value("author"));
+    if (!a.value("title").isEmpty())
+        model->setTitle(a.value("title"));
+    if (!a.value("comment").isEmpty())
+        model->setComment(unquoteMeta(a.value("comment")));
+    if (!a.value("branchCount").isEmpty()) {
+        branchesTotal = a.value("branchCount").toInt();
+        if (branchesTotal > 10) {
+            useProgress = true;
+            mainWindow->setProgressMaximum(branchesTotal);
+        }
+    }
+
+    if (!a.value("mapZoomFactor").isEmpty())
+        model->setMapZoomFactor(a.value("mapZoomFactor").toDouble());
+    if (!a.value("mapRotationAngle").isEmpty())
+        model->setMapRotationAngle(a.value("mapRotationAngle").toDouble());
+
+    return readMapDesignCompatibleAttr(a);
+}
+
+bool parseVYMHandler::readMapDesignCompatibleAttr( const QXmlAttributes &a)
+{
+    // Some attributes moved in version 2.9.514 from
+    // <vymmap> to <mapdesign> and <md>
+    // This code here will allow to parse also newer maps.
+    // Some elements though are not available in older versions, especially
+    // <frame frameUsage="outerFrame" ...>
+    // <branch rotHeading=... rotContent=... >
+
+    QColor col;
+    if (!a.value("backgroundColor").isEmpty()) {
+        col.setNamedColor(a.value("backgroundColor"));
+        model->setMapBackgroundColor(col);
+    }
+    if (!a.value("defaultFont").isEmpty()) {
+        QFont font;
+        font.fromString(a.value("defaultFont"));
+        model->setMapDefaultFont(font);
+    }
+    if (!a.value("selectionColor").isEmpty()) {
+        // Only for compatibility
+        col.setNamedColor(a.value("selectionColor"));
+        model->setSelectionBrushColor(col);
+        model->setSelectionPenColor(col);
+        model->setSelectionPenWidth(1);
+    }
+    if (!a.value("selectionPenColor").isEmpty()) {
+        // Introduced in 2.9.12
+        col.setNamedColor(a.value("selectionPenColor"));
+        model->setSelectionPenColor(col);
+    }
+    if (!a.value("selectionPenWidth").isEmpty()) {
+        // Introduced in 2.9.12
+        bool ok;
+        qreal w = a.value("selectionPenWidth").toFloat(&ok);
+        if (ok)
+            model->setSelectionPenWidth(w);
+    }
+    if (!a.value("selectionBrushColor").isEmpty()) {
+        // Introduced in 2.9.12
+        col.setNamedColor(a.value("selectionBrushColor"));
+        model->setSelectionBrushColor(col);
+    }
+    if (!a.value("linkColorHint").isEmpty()) {
+        if (a.value("linkColorHint") == "HeadingColor")
+            model->setMapLinkColorHint(LinkableMapObj::HeadingColor);
+        else
+            model->setMapLinkColorHint(LinkableMapObj::DefaultColor);
+    }
+    if (!a.value("linkStyle").isEmpty())
+        model->setMapLinkStyle(a.value("linkStyle"));
+    if (!a.value("linkColor").isEmpty()) {
+        col.setNamedColor(a.value("linkColor"));
+        model->setMapDefLinkColor(col);
+    }
+
+    QPen pen(model->getMapDefXLinkPen());
+    if (!a.value("defXLinkColor").isEmpty()) {
+        col.setNamedColor(a.value("defXLinkColor"));
+        pen.setColor(col);
+    }
+    if (!a.value("defXLinkWidth").isEmpty())
+        pen.setWidth(a.value("defXLinkWidth").toInt());
+    if (!a.value("defXLinkPenStyle").isEmpty()) {
+        bool ok;
+        Qt::PenStyle ps = penStyle(a.value("defXLinkPenStyle"), ok);
+        if (!ok)
+            return false;
+        pen.setStyle(ps);
+    }
+    model->setMapDefXLinkPen(pen);
+
+    if (!a.value("defXLinkStyleBegin").isEmpty())
+        model->setMapDefXLinkStyleBegin(a.value("defXLinkStyleBegin"));
+    if (!a.value("defXLinkStyleEnd").isEmpty())
+        model->setMapDefXLinkStyleEnd(a.value("defXLinkStyleEnd"));
+    return true;
+}
+
+bool parseVYMHandler::readBranchAttr(const QXmlAttributes &a)
+{
+    branchesCounter++;
+    if (useProgress)
+        mainWindow->addProgressValue((float)branchesCounter / branchesTotal);
+
+    lastMI = lastBranch;
+
+    if (!readOOAttr(a))
+        return false;
+
+    if (!a.value("scrolled").isEmpty())
+        lastBranch->toggleScroll();
+
+    if (!a.value("incImgV").isEmpty()) {
+        if (a.value("incImgV") == "true")
+            lastBranch->setIncludeImagesVer(true);
+        else
+            lastBranch->setIncludeImagesVer(false);
+    }
+    if (!a.value("incImgH").isEmpty()) {
+        if (a.value("incImgH") == "true")
+            lastBranch->setIncludeImagesHor(true);
+        else
+            lastBranch->setIncludeImagesHor(false);
+    }
+    if (a.value("childrenFreePos") == "true")
+        lastBranch->setChildrenLayout(BranchItem::FreePositioning);
+
+    return true;
+}
+
+bool parseVYMHandler::readFrameAttr(const QXmlAttributes &a)
+{
+    if (lastMI) {
+        OrnamentedObj *oo = (OrnamentedObj *)(lastMI->getLMO());
+        if (oo) {
+            bool ok;
+            int x;
+            {
+                if (!a.value("frameType").isEmpty())
+                    oo->setFrameType(a.value("frameType"));
+                if (!a.value("penColor").isEmpty())
+                    oo->setFramePenColor(a.value("penColor"));
+                if (!a.value("brushColor").isEmpty()) {
+                    oo->setFrameBrushColor(a.value("brushColor"));
+                    lastMI->setBackgroundColor(a.value("brushColor"));
+                }
+                if (!a.value("padding").isEmpty()) {
+                    x = a.value("padding").toInt(&ok);
+                    if (ok)
+                        oo->setFramePadding(x);
+                }
+                if (!a.value("borderWidth").isEmpty()) {
+                    x = a.value("borderWidth").toInt(&ok);
+                    if (ok)
+                        oo->setFrameBorderWidth(x);
+                }
+                if (!a.value("includeChildren").isEmpty()) {
+                    if (a.value("includeChildren") == "true")
+                        oo->setFrameIncludeChildren(true);
+                    else
+                        oo->setFrameIncludeChildren(false);
+                }
+            }
+            return true;
+        }
+    }
+    return false;
+}
+
+bool parseVYMHandler::readOOAttr(const QXmlAttributes &a)
+{
+    if (lastMI) {
+        bool okx, oky;
+        float x, y;
+        if (!a.value("posX").isEmpty()) {   // Introduced in 2.9.501, added here for file compatibility
+            if (!a.value("posY").isEmpty()) {
+                x = a.value("posX").toFloat(&okx);
+                y = a.value("posY").toFloat(&oky);
+                if (okx && oky)
+                    lastMI->setRelPos(QPointF(x, y));
+                else
+                    return false; // Couldn't read relPos
+            }
+        }
+        if (!a.value("relPosX").isEmpty()) {
+            if (!a.value("relPosY").isEmpty()) {
+                x = a.value("relPosX").toFloat(&okx);
+                y = a.value("relPosY").toFloat(&oky);
+                if (okx && oky)
+                    lastMI->setRelPos(QPointF(x, y));
+                else
+                    return false; // Couldn't read relPos
+            }
+        }
+        if (!a.value("absPosX").isEmpty()) {
+            if (!a.value("absPosY").isEmpty()) {
+                x = a.value("absPosX").toFloat(&okx);
+                y = a.value("absPosY").toFloat(&oky);
+                if (okx && oky)
+                    lastMI->setAbsPos(QPointF(x, y));
+                else
+                    return false; // Couldn't read absPos
+            }
+        }
+        if (!a.value("url").isEmpty())
+            lastMI->setURL(a.value("url"));
+        if (!a.value("vymLink").isEmpty())
+            lastMI->setVymLink(a.value("vymLink"));
+        if (!a.value("hideInExport").isEmpty())
+            if (a.value("hideInExport") == "true")
+                lastMI->setHideInExport(true);
+
+        if (!a.value("hideLink").isEmpty()) {
+            if (a.value("hideLink") == "true")
+                lastMI->setHideLinkUnselected(true);
+            else
+                lastMI->setHideLinkUnselected(false);
+        }
+
+        if (!a.value("localTarget").isEmpty())
+            if (a.value("localTarget") == "true")
+                lastMI->toggleTarget();
+        if (!a.value("rotation").isEmpty()) {
+            x = a.value("rotation").toFloat(&okx);
+            if (okx)
+                lastMI->setRotation(x);
+            else
+                return false; // Couldn't read rotation
+        }
+
+        if (!a.value("uuid").isEmpty()) {
+            // While pasting, check for existing UUID
+            if (loadMode != ImportAdd && !model->findUuid(a.value("uuid")))
+                lastMI->setUuid(a.value("uuid"));
+        }
+    }
+    return true;
+}
+
+bool parseVYMHandler::readNoteAttr(const QXmlAttributes &a)
+{ // only for backward compatibility (<1.4.6). Use htmlnote now.
+    vymtext.clear();
+    QString fn;
+    if (!a.value("href").isEmpty()) {
+        // Load note
+        fn = parseHREF(a.value("href"));
+        QFile file(fn);
+        QString s; // Reading a note
+
+        if (!file.open(QIODevice::ReadOnly)) {
+            qWarning() << "parseVYMHandler::readNoteAttr:  Couldn't load " + fn;
+            return false;
+        }
+        QTextStream stream(&file);
+        stream.setCodec("UTF-8");
+        QString lines;
+        while (!stream.atEnd()) {
+            lines += stream.readLine() + "\n";
+        }
+        file.close();
+
+        if (lines.contains("<html")) {
+            vymtext.setRichText(lines);
+        } else
+            vymtext.setPlainText(lines);
+    }
+    if (!a.value("fonthint").isEmpty())
+        vymtext.setFontHint(a.value("fonthint"));
+    return true;
+}
+
+bool parseVYMHandler::readImageAttr(const QXmlAttributes &a)
+{
+    lastMI = lastImage;
+
+    if (!readOOAttr(a))
+        return false;
+
+    if (!a.value("href").isEmpty()) {
+        // Load Image
+        if (!lastImage->load(parseHREF(a.value("href")))) {
+            QMessageBox::warning(0, "Warning: ",
+                                 "Couldn't load image\n" +
+                                     parseHREF(a.value("href")));
+            lastImage = NULL;
+            return true;
+        }
+    }
+    if (!a.value("zPlane").isEmpty())
+        lastImage->setZValue(a.value("zPlane").toInt());
+    float x, y;
+    bool okx, oky;
+    if (!a.value("posX").isEmpty()) {   // Introduced in 2.9.501, added here for file compatibility
+        if (!a.value("posY").isEmpty()) {
+            x = a.value("posX").toFloat(&okx);
+            y = a.value("posY").toFloat(&oky);
+            if (okx && oky)
+                lastImage->setRelPos(QPointF(x, y));
+            else
+                return false; // Couldn't read relPos
+        }
+    }
+    if (!a.value("relPosX").isEmpty()) {
+        if (!a.value("relPosY").isEmpty()) {
+            // read relPos
+            x = a.value("relPosX").toFloat(&okx);
+            y = a.value("relPosY").toFloat(&oky);
+            if (okx && oky)
+                lastImage->setRelPos(QPointF(x, y));
+            else
+                // Couldn't read relPos
+                return false;
+        }
+    }
+
+    // Scale image
+    // scaleX and scaleY are no longer used since 2.7.509 and replaced by
+    // scaleFactor
+    x = y = 1;
+    if (!a.value("scaleX").isEmpty()) {
+        x = a.value("scaleX").toFloat(&okx);
+        if (!okx)
+            return false;
+    }
+
+    if (!a.value("scaleY").isEmpty()) {
+        x = a.value("scaleY").toFloat(&oky);
+        if (!oky)
+            return false;
+    }
+
+    if (!a.value("scaleFactor").isEmpty()) {
+        x = a.value("scaleFactor").toFloat(&okx);
+        if (!okx)
+            return false;
+    }
+
+    if (x != 1)
+        lastImage->setScaleFactor(x);
+
+    if (!readOOAttr(a))
+        return false;
+
+    if (!a.value("originalName").isEmpty())
+    {
+        lastImage->setOriginalFilename(a.value("originalName"));
+    }
+    return true;
+}
+
+bool parseVYMHandler::readXLinkAttr(const QXmlAttributes &a)
+{
+    // Obsolete, see also readLinkAttr
+
+    if (!a.value("beginID").isEmpty()) {
+        if (!a.value("endID").isEmpty()) {
+            TreeItem *beginBI = model->findBySelectString(a.value("beginID"));
+            TreeItem *endBI = model->findBySelectString(a.value("endID"));
+            if (beginBI && endBI && beginBI->isBranchLikeType() &&
+                endBI->isBranchLikeType()) {
+                Link *li = new Link(model);
+                li->setBeginBranch((BranchItem *)beginBI);
+                li->setEndBranch((BranchItem *)endBI);
+                QPen pen = li->getPen();
+
+                if (!a.value("color").isEmpty()) {
+                    QColor col;
+                    col.setNamedColor(a.value("color"));
+                    pen.setColor(col);
+                }
+
+                if (!a.value("width").isEmpty()) {
+                    bool okx;
+                    pen.setWidth(a.value("width").toInt(&okx, 10));
+                }
+                li->setPen(pen);
+                model->createLink(li);
+            }
+        }
+    }
+    return true;
+}
+
+bool parseVYMHandler::readLinkNewAttr(const QXmlAttributes &a)
+{
+    // object ID is used starting in version 1.8.76
+    // (before there was beginBranch and endBranch)
+    //
+    // Starting in 1.13.2 xlinks are no longer subitems of branches,
+    // but listed at the end of the data in a map. This makes handling
+    // of links much safer and easier
+
+    if (!a.value("beginID").isEmpty()) {
+        if (!a.value("endID").isEmpty()) {
+            TreeItem *beginBI = model->findBySelectString(a.value("beginID"));
+            TreeItem *endBI = model->findBySelectString(a.value("endID"));
+            if (beginBI && endBI && beginBI->isBranchLikeType() &&
+                endBI->isBranchLikeType()) {
+                Link *li = new Link(model);
+                li->setBeginBranch((BranchItem *)beginBI);
+                li->setEndBranch((BranchItem *)endBI);
+
+                model->createLink(li);
+
+                bool okx;
+                QPen pen = li->getPen();
+                if (!a.value("type").isEmpty()) {
+                    li->setLinkType(a.value("type"));
+                }
+                if (!a.value("color").isEmpty()) {
+                    QColor col;
+                    col.setNamedColor(a.value("color"));
+                    pen.setColor(col);
+                }
+                if (!a.value("width").isEmpty()) {
+                    pen.setWidth(a.value("width").toInt(&okx, 10));
+                }
+                if (!a.value("penstyle").isEmpty()) {
+                    pen.setStyle(penStyle(a.value("penstyle"), okx));
+                }
+                li->setPen(pen);
+
+                if (!a.value("styleBegin").isEmpty())
+                    li->setStyleBegin(a.value("styleBegin"));
+                if (!a.value("styleEnd").isEmpty())
+                    li->setStyleEnd(a.value("styleEnd"));
+
+                XLinkObj *xlo = (XLinkObj *)(li->getMO());
+                if (xlo && !a.value("c0").isEmpty()) {
+                    QPointF p = point(a.value("c0"), okx);
+                    if (okx)
+                        xlo->setC0(p);
+                }
+                if (xlo && !a.value("c1").isEmpty()) {
+                    QPointF p = point(a.value("c1"), okx);
+                    if (okx)
+                        xlo->setC1(p);
+                }
+            }
+        }
+    }
+    return true;
+}
+
+bool parseVYMHandler::readSettingAttr(const QXmlAttributes &a)
+{
+    if (!a.value("key").isEmpty()) {
+        lastSetting = a.value("key");
+        if (!a.value("value").isEmpty())
+            settings.setLocalValue(
+                    model->getDestPath(), a.value("key"),
+                    a.value("value"));
+        else
+            return false;
+    }
+    else
+        return false;
+
+    return true;
+}
+
+bool parseVYMHandler::readSlideAttr(const QXmlAttributes &a)
+{
+    QStringList scriptlines; // FIXME-3 needed for switching to inScript
+                             // Most attributes are obsolete with inScript
+    if (!lastSlide)
+        return false;
+    {
+        if (!a.value("name").isEmpty())
+            lastSlide->setName(a.value("name"));
+        if (!a.value("zoom").isEmpty()) {
+            bool ok;
+            qreal z = a.value("zoom").toDouble(&ok);
+            if (!ok)
+                return false;
+            scriptlines.append(QString("setMapZoom(%1)").arg(z));
+        }
+        if (!a.value("rotation").isEmpty()) {
+            bool ok;
+            qreal z = a.value("rotation").toDouble(&ok);
+            if (!ok)
+                return false;
+            scriptlines.append(QString("setMapRotation(%1)").arg(z));
+        }
+        if (!a.value("duration").isEmpty()) {
+            bool ok;
+            int d = a.value("duration").toInt(&ok);
+            if (!ok)
+                return false;
+            scriptlines.append(QString("setMapAnimDuration(%1)").arg(d));
+        }
+        if (!a.value("curve").isEmpty()) {
+            bool ok;
+            int i = a.value("curve").toInt(&ok);
+            if (!ok)
+                return false;
+            if (i < 0 || i > QEasingCurve::OutInBounce)
+                return false;
+            scriptlines.append(QString("setMapAnimCurve(%1)").arg(i));
+        }
+        if (!a.value("mapitem").isEmpty()) {
+            TreeItem *ti = model->findBySelectString(a.value("mapitem"));
+            if (!ti)
+                return false;
+            scriptlines.append(
+                QString("centerOnID(\"%1\")").arg(ti->getUuid().toString()));
+        }
+        if (!a.value("inScript").isEmpty()) {
+            lastSlide->setInScript(unquoteMeta(a.value("inScript")));
+        }
+        else
+            lastSlide->setInScript(unquoteMeta(scriptlines.join(";\n")));
+
+        if (!a.value("outScript").isEmpty()) {
+            lastSlide->setOutScript(unquoteMeta(a.value("outScript")));
+        }
+    }
+    return true;
+}
+
+bool parseVYMHandler::readTaskAttr(const QXmlAttributes &a)
+{
+    if (!lastTask)
+        return false;
+    {
+        if (!a.value("status").isEmpty())
+            lastTask->setStatus(a.value("status"));
+        if (!a.value("awake").isEmpty())
+            lastTask->setAwake(a.value("awake"));
+        if (!a.value("date_creation").isEmpty())
+            lastTask->setDateCreation(a.value("date_creation"));
+        if (!a.value("date_modification").isEmpty())
+            lastTask->setDateModification(a.value("date_modification"));
+        if (!a.value("date_sleep").isEmpty()) {
+            if (!lastTask->setDateSleep(a.value("date_sleep")))
+                return false;
+        }
+        if (!a.value("prio_delta").isEmpty()) {
+            lastTask->setPriorityDelta(a.value("prio_delta").toInt());
+        }
+    }
+    return true;
+}
+
+bool parseVYMHandler::readUserFlagDefAttr(const QXmlAttributes &a)
+{
+    QString name;
+    QString path;
+    QString tooltip;
+    QUuid uid;
+
+    if (!a.value("name").isEmpty())
+        name = a.value("name");
+    if (!a.value("tooltip").isEmpty())
+        tooltip = a.value("tooltip");
+    if (!a.value("uuid").isEmpty())
+        uid = QUuid(a.value("uuid"));
+
+    Flag *flag;
+
+    if (!a.value("href").isEmpty()) {
+        // Setup flag with image
+        flag = mainWindow->setupFlag(parseHREF(a.value("href")), Flag::UserFlag,
+                                     name, tooltip, uid);
+    }
+    else {
+        qWarning() << "readUserFlagDefAttr:  Couldn't read href of flag "
+                   << a.value("name");
+        return false;
+    }
+
+    if (!a.value("group").isEmpty())
+        flag->setGroup(a.value("group"));
+
+    return true;
+}
+
+bool parseVYMHandler::readUserFlagAttr(const QXmlAttributes &a)
+{
+    QString name;
+    QString uuid;
+
+    if (!a.value("name").isEmpty())
+        name = a.value("name");
+    if (!a.value("uuid").isEmpty())
+        uuid = a.value("uuid");
+
+    lastBranch->toggleFlagByUid(QUuid(uuid));
+
+    return true;
+}