]> git.sesse.net Git - kdenlive/blob - src/documentvalidator.cpp
Optimize document check on opening
[kdenlive] / src / documentvalidator.cpp
1 /***************************************************************************
2  *   Copyright (C) 2007 by Jean-Baptiste Mardelle (jb@kdenlive.org)        *
3  *                                                                         *
4  *   This program is free software; you can redistribute it and/or modify  *
5  *   it under the terms of the GNU General Public License as published by  *
6  *   the Free Software Foundation; either version 2 of the License, or     *
7  *   (at your option) any later version.                                   *
8  *                                                                         *
9  *   This program is distributed in the hope that it will be useful,       *
10  *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
11  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
12  *   GNU General Public License for more details.                          *
13  *                                                                         *
14  *   You should have received a copy of the GNU General Public License     *
15  *   along with this program; if not, write to the                         *
16  *   Free Software Foundation, Inc.,                                       *
17  *   51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA          *
18  ***************************************************************************/
19
20
21 #include "documentvalidator.h"
22 #include "definitions.h"
23 #include "initeffects.h"
24 #include "mainwindow.h"
25
26 #include <KDebug>
27 #include <KMessageBox>
28 #include <KApplication>
29 #include <KLocale>
30 #include <KUrl>
31 #include <KStandardDirs>
32
33 #include <QFile>
34 #include <QColor>
35 #include <QString>
36 #include <QDir>
37 #include <QScriptEngine>
38
39 #include <mlt++/Mlt.h>
40
41 #include "locale.h"
42
43
44 DocumentValidator::DocumentValidator(QDomDocument doc):
45         m_doc(doc),
46         m_modified(false)
47 {}
48
49 bool DocumentValidator::validate(const double currentVersion)
50 {
51     QDomElement mlt = m_doc.firstChildElement("mlt");
52     // At least the root element must be there
53     if (mlt.isNull())
54         return false;
55
56     QDomElement kdenliveDoc = mlt.firstChildElement("kdenlivedoc");
57     // Check if we're validating a Kdenlive project
58     if (kdenliveDoc.isNull())
59         return false;
60
61     // Previous MLT / Kdenlive versions used C locale by default
62     QLocale documentLocale = QLocale::c();
63     
64     if (mlt.hasAttribute("LC_NUMERIC")) {
65         // Set locale for the document
66         // WARNING: what should be done in case the locale does not exist on the system?
67         setlocale(LC_NUMERIC, mlt.attribute("LC_NUMERIC").toUtf8().constData());
68         documentLocale = QLocale(mlt.attribute("LC_NUMERIC"));
69     }
70     
71     documentLocale.setNumberOptions(QLocale::OmitGroupSeparator);
72     if (documentLocale.decimalPoint() != QLocale().decimalPoint()) {
73         // If loading an older MLT file without LC_NUMERIC, set locale to C which was previously the default
74         if (!mlt.hasAttribute("LC_NUMERIC")) setlocale(LC_NUMERIC, "C");
75
76         QLocale::setDefault(documentLocale);
77         // locale conversion might need to be redone
78         initEffects::parseEffectFiles();
79     }
80
81     // TODO: remove after string freeze
82     if (0)
83         KMessageBox::sorry(kapp->activeWindow(), i18n("The document you are opening uses a different locale (%1) than your system. You can only open and render it, no editing is supported unless you change your system's locale.", mlt.attribute("LC_NUMERIC")), i18n("Read only project"));
84
85     // Upgrade the document to the latest version
86     if (!upgrade(documentLocale.toDouble(kdenliveDoc.attribute("version")), currentVersion))
87         return false;
88
89     /*
90      * Check the syntax (this will be replaced by XSD validation with Qt 4.6)
91      * and correct some errors
92      */
93     {
94         // Return (or create) the tractor
95         QDomElement tractor = mlt.firstChildElement("tractor");
96         if (tractor.isNull()) {
97             m_modified = true;
98             tractor = m_doc.createElement("tractor");
99             tractor.setAttribute("global_feed", "1");
100             tractor.setAttribute("in", "0");
101             tractor.setAttribute("out", "-1");
102             tractor.setAttribute("id", "maintractor");
103             mlt.appendChild(tractor);
104         }
105
106         /*
107          * Make sure at least one track exists, and they're equal in number to
108          * to the maximum between MLT and Kdenlive playlists and tracks
109          */
110         QDomNodeList playlists = m_doc.elementsByTagName("playlist");
111         int tracksMax = playlists.count() - 1; // Remove the black track
112         QDomNodeList tracks = tractor.elementsByTagName("track");
113         tracksMax = qMax(tracks.count() - 1, tracksMax);
114         QDomNodeList tracksinfo = kdenliveDoc.elementsByTagName("trackinfo");
115         tracksMax = qMax(tracksinfo.count(), tracksMax);
116         tracksMax = qMax(1, tracksMax); // Force existance of one track
117         if (playlists.count() - 1 < tracksMax ||
118                 tracks.count() - 1 < tracksMax ||
119                 tracksinfo.count() < tracksMax) {
120             kDebug() << "//// WARNING, PROJECT IS CORRUPTED, MISSING TRACK";
121             m_modified = true;
122             int difference;
123             // use the MLT tracks as reference
124             if (tracks.count() - 1 < tracksMax) {
125                 // Looks like one MLT track is missing, remove the extra Kdenlive track if there is one.
126                 if (tracksinfo.count() != tracks.count() - 1) {
127                     // The Kdenlive tracks are not ok, clear and rebuild them
128                     QDomNode tinfo = kdenliveDoc.firstChildElement("tracksinfo");
129                     QDomNode tnode = tinfo.firstChild();
130                     while (!tnode.isNull()) {
131                         tinfo.removeChild(tnode);
132                         tnode = tinfo.firstChild();
133                     }
134
135                     for (int i = 1; i < tracks.count(); i++) {
136                         QString hide = tracks.at(i).toElement().attribute("hide");
137                         QDomElement newTrack = m_doc.createElement("trackinfo");
138                         if (hide == "video") {
139                             // audio track;
140                             newTrack.setAttribute("type", "audio");
141                             newTrack.setAttribute("blind", 1);
142                             newTrack.setAttribute("mute", 0);
143                             newTrack.setAttribute("lock", 0);
144                         } else {
145                             newTrack.setAttribute("blind", 0);
146                             newTrack.setAttribute("mute", 0);
147                             newTrack.setAttribute("lock", 0);
148                         }
149                         tinfo.appendChild(newTrack);
150                     }
151                 }
152             }
153
154             if (playlists.count() - 1 < tracksMax) {
155                 difference = tracksMax - (playlists.count() - 1);
156                 for (int i = 0; i < difference; ++i) {
157                     QDomElement playlist = m_doc.createElement("playlist");
158                     mlt.appendChild(playlist);
159                 }
160             }
161             if (tracks.count() - 1 < tracksMax) {
162                 difference = tracksMax - (tracks.count() - 1);
163                 for (int i = 0; i < difference; ++i) {
164                     QDomElement track = m_doc.createElement("track");
165                     tractor.appendChild(track);
166                 }
167             }
168             if (tracksinfo.count() < tracksMax) {
169                 QDomElement tracksinfoElm = kdenliveDoc.firstChildElement("tracksinfo");
170                 if (tracksinfoElm.isNull()) {
171                     tracksinfoElm = m_doc.createElement("tracksinfo");
172                     kdenliveDoc.appendChild(tracksinfoElm);
173                 }
174                 difference = tracksMax - tracksinfo.count();
175                 for (int i = 0; i < difference; ++i) {
176                     QDomElement trackinfo = m_doc.createElement("trackinfo");
177                     trackinfo.setAttribute("mute", "0");
178                     trackinfo.setAttribute("locked", "0");
179                     tracksinfoElm.appendChild(trackinfo);
180                 }
181             }
182         }        
183
184         // TODO: check the tracks references
185         // TODO: check internal mix transitions
186         
187     }
188
189     updateEffects();
190
191     return true;
192 }
193
194 bool DocumentValidator::upgrade(double version, const double currentVersion)
195 {
196     kDebug() << "Opening a document with version " << version;
197
198     // No conversion needed
199     if (version == currentVersion) {
200         return true;
201     }
202
203     // The document is too new
204     if (version > currentVersion) {
205         kDebug() << "Unable to open document with version " << version;
206         KMessageBox::sorry(kapp->activeWindow(), i18n("This project type is unsupported (version %1) and can't be loaded.\nPlease consider upgrading your Kdenlive version.", version), i18n("Unable to open project"));
207         return false;
208     }
209
210     // Unsupported document versions
211     if (version == 0.5 || version == 0.7) {
212         kDebug() << "Unable to open document with version " << version;
213         KMessageBox::sorry(kapp->activeWindow(), i18n("This project type is unsupported (version %1) and can't be loaded.", version), i18n("Unable to open project"));
214         return false;
215     }
216
217     // <kdenlivedoc />
218     QDomNode infoXmlNode = m_doc.elementsByTagName("kdenlivedoc").at(0);
219     QDomElement infoXml = infoXmlNode.toElement();
220     infoXml.setAttribute("upgraded", "1");
221
222     if (version <= 0.6) {
223         QDomElement infoXml_old = infoXmlNode.cloneNode(true).toElement(); // Needed for folders
224         QDomNode westley = m_doc.elementsByTagName("westley").at(1);
225         QDomNode tractor = m_doc.elementsByTagName("tractor").at(0);
226         QDomNode multitrack = m_doc.elementsByTagName("multitrack").at(0);
227         QDomNodeList playlists = m_doc.elementsByTagName("playlist");
228
229         QDomNode props = m_doc.elementsByTagName("properties").at(0).toElement();
230         QString profile = props.toElement().attribute("videoprofile");
231         int startPos = props.toElement().attribute("timeline_position").toInt();
232         if (profile == "dv_wide")
233             profile = "dv_pal_wide";
234
235         // move playlists outside of tractor and add the tracks instead
236         int max = playlists.count();
237         if (westley.isNull()) {
238             westley = m_doc.createElement("westley");
239             m_doc.documentElement().appendChild(westley);
240         }
241         if (tractor.isNull()) {
242             kDebug() << "// NO MLT PLAYLIST, building empty one";
243             QDomElement blank_tractor = m_doc.createElement("tractor");
244             westley.appendChild(blank_tractor);
245             QDomElement blank_playlist = m_doc.createElement("playlist");
246             blank_playlist.setAttribute("id", "black_track");
247             westley.insertBefore(blank_playlist, QDomNode());
248             QDomElement blank_track = m_doc.createElement("track");
249             blank_track.setAttribute("producer", "black_track");
250             blank_tractor.appendChild(blank_track);
251
252             QDomNodeList kdenlivetracks = m_doc.elementsByTagName("kdenlivetrack");
253             for (int i = 0; i < kdenlivetracks.count(); i++) {
254                 blank_playlist = m_doc.createElement("playlist");
255                 blank_playlist.setAttribute("id", "playlist" + QString::number(i));
256                 westley.insertBefore(blank_playlist, QDomNode());
257                 blank_track = m_doc.createElement("track");
258                 blank_track.setAttribute("producer", "playlist" + QString::number(i));
259                 blank_tractor.appendChild(blank_track);
260                 if (kdenlivetracks.at(i).toElement().attribute("cliptype") == "Sound") {
261                     blank_playlist.setAttribute("hide", "video");
262                     blank_track.setAttribute("hide", "video");
263                 }
264             }
265         } else for (int i = 0; i < max; i++) {
266                 QDomNode n = playlists.at(i);
267                 westley.insertBefore(n, QDomNode());
268                 QDomElement pl = n.toElement();
269                 QDomElement track = m_doc.createElement("track");
270                 QString trackType = pl.attribute("hide");
271                 if (!trackType.isEmpty())
272                     track.setAttribute("hide", trackType);
273                 QString playlist_id =  pl.attribute("id");
274                 if (playlist_id.isEmpty()) {
275                     playlist_id = "black_track";
276                     pl.setAttribute("id", playlist_id);
277                 }
278                 track.setAttribute("producer", playlist_id);
279                 //tractor.appendChild(track);
280 #define KEEP_TRACK_ORDER 1
281 #ifdef KEEP_TRACK_ORDER
282                 tractor.insertAfter(track, QDomNode());
283 #else
284                 // Insert the new track in an order that hopefully matches the 3 video, then 2 audio tracks of Kdenlive 0.7.0
285                 // insertion sort - O( tracks*tracks )
286                 // Note, this breaks _all_ transitions - but you can move them up and down afterwards.
287                 QDomElement tractor_elem = tractor.toElement();
288                 if (! tractor_elem.isNull()) {
289                     QDomNodeList tracks = tractor_elem.elementsByTagName("track");
290                     int size = tracks.size();
291                     if (size == 0) {
292                         tractor.insertAfter(track, QDomNode());
293                     } else {
294                         bool inserted = false;
295                         for (int i = 0; i < size; ++i) {
296                             QDomElement track_elem = tracks.at(i).toElement();
297                             if (track_elem.isNull()) {
298                                 tractor.insertAfter(track, QDomNode());
299                                 inserted = true;
300                                 break;
301                             } else {
302                                 kDebug() << "playlist_id: " << playlist_id << " producer:" << track_elem.attribute("producer");
303                                 if (playlist_id < track_elem.attribute("producer")) {
304                                     tractor.insertBefore(track, track_elem);
305                                     inserted = true;
306                                     break;
307                                 }
308                             }
309                         }
310                         // Reach here, no insertion, insert last
311                         if (!inserted) {
312                             tractor.insertAfter(track, QDomNode());
313                         }
314                     }
315                 } else {
316                     kWarning() << "tractor was not a QDomElement";
317                     tractor.insertAfter(track, QDomNode());
318                 }
319 #endif
320             }
321         tractor.removeChild(multitrack);
322
323         // audio track mixing transitions should not be added to track view, so add required attribute
324         QDomNodeList transitions = m_doc.elementsByTagName("transition");
325         max = transitions.count();
326         for (int i = 0; i < max; i++) {
327             QDomElement tr = transitions.at(i).toElement();
328             if (tr.attribute("combine") == "1" && tr.attribute("mlt_service") == "mix") {
329                 QDomElement property = m_doc.createElement("property");
330                 property.setAttribute("name", "internal_added");
331                 QDomText value = m_doc.createTextNode("237");
332                 property.appendChild(value);
333                 tr.appendChild(property);
334                 property = m_doc.createElement("property");
335                 property.setAttribute("name", "mlt_service");
336                 value = m_doc.createTextNode("mix");
337                 property.appendChild(value);
338                 tr.appendChild(property);
339             } else {
340                 // convert transition
341                 QDomNamedNodeMap attrs = tr.attributes();
342                 for (int j = 0; j < attrs.count(); j++) {
343                     QString attrName = attrs.item(j).nodeName();
344                     if (attrName != "in" && attrName != "out" && attrName != "id") {
345                         QDomElement property = m_doc.createElement("property");
346                         property.setAttribute("name", attrName);
347                         QDomText value = m_doc.createTextNode(attrs.item(j).nodeValue());
348                         property.appendChild(value);
349                         tr.appendChild(property);
350                     }
351                 }
352             }
353         }
354
355         // move transitions after tracks
356         for (int i = 0; i < max; i++) {
357             tractor.insertAfter(transitions.at(0), QDomNode());
358         }
359
360         // Fix filters format
361         QDomNodeList entries = m_doc.elementsByTagName("entry");
362         max = entries.count();
363         for (int i = 0; i < max; i++) {
364             QString last_id;
365             int effectix = 0;
366             QDomNode m = entries.at(i).firstChild();
367             while (!m.isNull()) {
368                 if (m.toElement().tagName() == "filter") {
369                     QDomElement filt = m.toElement();
370                     QDomNamedNodeMap attrs = filt.attributes();
371                     QString current_id = filt.attribute("kdenlive_id");
372                     if (current_id != last_id) {
373                         effectix++;
374                         last_id = current_id;
375                     }
376                     QDomElement e = m_doc.createElement("property");
377                     e.setAttribute("name", "kdenlive_ix");
378                     QDomText value = m_doc.createTextNode(QString::number(effectix));
379                     e.appendChild(value);
380                     filt.appendChild(e);
381                     for (int j = 0; j < attrs.count(); j++) {
382                         QDomAttr a = attrs.item(j).toAttr();
383                         if (!a.isNull()) {
384                             kDebug() << " FILTER; adding :" << a.name() << ":" << a.value();
385                             QDomElement e = m_doc.createElement("property");
386                             e.setAttribute("name", a.name());
387                             QDomText value = m_doc.createTextNode(a.value());
388                             e.appendChild(value);
389                             filt.appendChild(e);
390
391                         }
392                     }
393                 }
394                 m = m.nextSibling();
395             }
396         }
397
398         /*
399             QDomNodeList filters = m_doc.elementsByTagName("filter");
400             max = filters.count();
401             QString last_id;
402             int effectix = 0;
403             for (int i = 0; i < max; i++) {
404                 QDomElement filt = filters.at(i).toElement();
405                 QDomNamedNodeMap attrs = filt.attributes();
406         QString current_id = filt.attribute("kdenlive_id");
407         if (current_id != last_id) {
408             effectix++;
409             last_id = current_id;
410         }
411         QDomElement e = m_doc.createElement("property");
412                 e.setAttribute("name", "kdenlive_ix");
413                 QDomText value = m_doc.createTextNode(QString::number(1));
414                 e.appendChild(value);
415                 filt.appendChild(e);
416                 for (int j = 0; j < attrs.count(); j++) {
417                     QDomAttr a = attrs.item(j).toAttr();
418                     if (!a.isNull()) {
419                         kDebug() << " FILTER; adding :" << a.name() << ":" << a.value();
420                         QDomElement e = m_doc.createElement("property");
421                         e.setAttribute("name", a.name());
422                         QDomText value = m_doc.createTextNode(a.value());
423                         e.appendChild(value);
424                         filt.appendChild(e);
425                     }
426                 }
427             }*/
428
429         // fix slowmotion
430         QDomNodeList producers = westley.toElement().elementsByTagName("producer");
431         max = producers.count();
432         for (int i = 0; i < max; i++) {
433             QDomElement prod = producers.at(i).toElement();
434             if (prod.attribute("mlt_service") == "framebuffer") {
435                 QString slowmotionprod = prod.attribute("resource");
436                 slowmotionprod.replace(':', '?');
437                 kDebug() << "// FOUND WRONG SLOWMO, new: " << slowmotionprod;
438                 prod.setAttribute("resource", slowmotionprod);
439             }
440         }
441         // move producers to correct place, markers to a global list, fix clip descriptions
442         QDomElement markers = m_doc.createElement("markers");
443         // This will get the xml producers:
444         producers = m_doc.elementsByTagName("producer");
445         max = producers.count();
446         for (int i = 0; i < max; i++) {
447             QDomElement prod = producers.at(0).toElement();
448             // add resource also as a property (to allow path correction in setNewResource())
449             // TODO: will it work with slowmotion? needs testing
450             /*if (!prod.attribute("resource").isEmpty()) {
451                 QDomElement prop_resource = m_doc.createElement("property");
452                 prop_resource.setAttribute("name", "resource");
453                 QDomText resource = m_doc.createTextNode(prod.attribute("resource"));
454                 prop_resource.appendChild(resource);
455                 prod.appendChild(prop_resource);
456             }*/
457             QDomNode m = prod.firstChild();
458             if (!m.isNull()) {
459                 if (m.toElement().tagName() == "markers") {
460                     QDomNodeList prodchilds = m.childNodes();
461                     int maxchild = prodchilds.count();
462                     for (int k = 0; k < maxchild; k++) {
463                         QDomElement mark = prodchilds.at(0).toElement();
464                         mark.setAttribute("id", prod.attribute("id"));
465                         markers.insertAfter(mark, QDomNode());
466                     }
467                     prod.removeChild(m);
468                 } else if (prod.attribute("type").toInt() == TEXT) {
469                     // convert title clip
470                     if (m.toElement().tagName() == "textclip") {
471                         QDomDocument tdoc;
472                         QDomElement titleclip = m.toElement();
473                         QDomElement title = tdoc.createElement("kdenlivetitle");
474                         tdoc.appendChild(title);
475                         QDomNodeList objects = titleclip.childNodes();
476                         int maxchild = objects.count();
477                         for (int k = 0; k < maxchild; k++) {
478                             QString objectxml;
479                             QDomElement ob = objects.at(k).toElement();
480                             if (ob.attribute("type") == "3") {
481                                 // text object - all of this goes into "xmldata"...
482                                 QDomElement item = tdoc.createElement("item");
483                                 item.setAttribute("z-index", ob.attribute("z"));
484                                 item.setAttribute("type", "QGraphicsTextItem");
485                                 QDomElement position = tdoc.createElement("position");
486                                 position.setAttribute("x", ob.attribute("x"));
487                                 position.setAttribute("y", ob.attribute("y"));
488                                 QDomElement content = tdoc.createElement("content");
489                                 content.setAttribute("font", ob.attribute("font_family"));
490                                 content.setAttribute("font-size", ob.attribute("font_size"));
491                                 content.setAttribute("font-bold", ob.attribute("bold"));
492                                 content.setAttribute("font-italic", ob.attribute("italic"));
493                                 content.setAttribute("font-underline", ob.attribute("underline"));
494                                 QString col = ob.attribute("color");
495                                 QColor c(col);
496                                 content.setAttribute("font-color", colorToString(c));
497                                 // todo: These fields are missing from the newly generated xmldata:
498                                 // transform, startviewport, endviewport, background
499
500                                 QDomText conttxt = tdoc.createTextNode(ob.attribute("text"));
501                                 content.appendChild(conttxt);
502                                 item.appendChild(position);
503                                 item.appendChild(content);
504                                 title.appendChild(item);
505                             } else if (ob.attribute("type") == "5") {
506                                 // rectangle object
507                                 QDomElement item = tdoc.createElement("item");
508                                 item.setAttribute("z-index", ob.attribute("z"));
509                                 item.setAttribute("type", "QGraphicsRectItem");
510                                 QDomElement position = tdoc.createElement("position");
511                                 position.setAttribute("x", ob.attribute("x"));
512                                 position.setAttribute("y", ob.attribute("y"));
513                                 QDomElement content = tdoc.createElement("content");
514                                 QString col = ob.attribute("color");
515                                 QColor c(col);
516                                 content.setAttribute("brushcolor", colorToString(c));
517                                 QString rect = "0,0,";
518                                 rect.append(ob.attribute("width"));
519                                 rect.append(",");
520                                 rect.append(ob.attribute("height"));
521                                 content.setAttribute("rect", rect);
522                                 item.appendChild(position);
523                                 item.appendChild(content);
524                                 title.appendChild(item);
525                             }
526                         }
527                         prod.setAttribute("xmldata", tdoc.toString());
528                         // mbd todo: This clearly does not work, as every title gets the same name - trying to leave it empty
529                         // QStringList titleInfo = TitleWidget::getFreeTitleInfo(projectFolder());
530                         // prod.setAttribute("titlename", titleInfo.at(0));
531                         // prod.setAttribute("resource", titleInfo.at(1));
532                         //kDebug()<<"TITLE DATA:\n"<<tdoc.toString();
533                         prod.removeChild(m);
534                     } // End conversion of title clips.
535
536                 } else if (m.isText()) {
537                     QString comment = m.nodeValue();
538                     if (!comment.isEmpty()) {
539                         prod.setAttribute("description", comment);
540                     }
541                     prod.removeChild(m);
542                 }
543             }
544             int duration = prod.attribute("duration").toInt();
545             if (duration > 0) prod.setAttribute("out", QString::number(duration));
546             // The clip goes back in, but text clips should not go back in, at least not modified
547             westley.insertBefore(prod, QDomNode());
548         }
549
550         QDomNode westley0 = m_doc.elementsByTagName("westley").at(0);
551         if (!markers.firstChild().isNull()) westley0.appendChild(markers);
552
553         /*
554          * Convert as much of the kdenlivedoc as possible. Use the producer in
555          * westley. First, remove the old stuff from westley, and add a new
556          * empty one. Also, track the max id in order to use it for the adding
557          * of groups/folders
558          */
559         int max_kproducer_id = 0;
560         westley0.removeChild(infoXmlNode);
561         QDomElement infoXml_new = m_doc.createElement("kdenlivedoc");
562         infoXml_new.setAttribute("profile", profile);
563         infoXml.setAttribute("position", startPos);
564
565         // Add all the producers that has a resource in westley
566         QDomElement westley_element = westley0.toElement();
567         if (westley_element.isNull()) {
568             kWarning() << "westley0 element in document was not a QDomElement - unable to add producers to new kdenlivedoc";
569         } else {
570             QDomNodeList wproducers = westley_element.elementsByTagName("producer");
571             int kmax = wproducers.count();
572             for (int i = 0; i < kmax; i++) {
573                 QDomElement wproducer = wproducers.at(i).toElement();
574                 if (wproducer.isNull()) {
575                     kWarning() << "Found producer in westley0, that was not a QDomElement";
576                     continue;
577                 }
578                 if (wproducer.attribute("id") == "black") continue;
579                 // We have to do slightly different things, depending on the type
580                 kDebug() << "Converting producer element with type" << wproducer.attribute("type");
581                 if (wproducer.attribute("type").toInt() == TEXT) {
582                     kDebug() << "Found TEXT element in producer" << endl;
583                     QDomElement kproducer = wproducer.cloneNode(true).toElement();
584                     kproducer.setTagName("kdenlive_producer");
585                     infoXml_new.appendChild(kproducer);
586                     /*
587                      * TODO: Perhaps needs some more changes here to
588                      * "frequency", aspect ratio as a float, frame_size,
589                      * channels, and later, resource and title name
590                      */
591                 } else {
592                     QDomElement kproducer = m_doc.createElement("kdenlive_producer");
593                     kproducer.setAttribute("id", wproducer.attribute("id"));
594                     if (!wproducer.attribute("description").isEmpty())
595                         kproducer.setAttribute("description", wproducer.attribute("description"));
596                     kproducer.setAttribute("resource", wproducer.attribute("resource"));
597                     kproducer.setAttribute("type", wproducer.attribute("type"));
598                     // Testing fix for 358
599                     if (!wproducer.attribute("aspect_ratio").isEmpty()) {
600                         kproducer.setAttribute("aspect_ratio", wproducer.attribute("aspect_ratio"));
601                     }
602                     if (!wproducer.attribute("source_fps").isEmpty()) {
603                         kproducer.setAttribute("fps", wproducer.attribute("source_fps"));
604                     }
605                     if (!wproducer.attribute("length").isEmpty()) {
606                         kproducer.setAttribute("duration", wproducer.attribute("length"));
607                     }
608                     infoXml_new.appendChild(kproducer);
609                 }
610                 if (wproducer.attribute("id").toInt() > max_kproducer_id) {
611                     max_kproducer_id = wproducer.attribute("id").toInt();
612                 }
613             }
614         }
615 #define LOOKUP_FOLDER 1
616 #ifdef LOOKUP_FOLDER
617         /*
618          * Look through all the folder elements of the old doc, for each folder,
619          * for each producer, get the id, look it up in the new doc, set the
620          * groupname and groupid. Note, this does not work at the moment - at
621          * least one folder shows up missing, and clips with no folder does not
622          * show up.
623          */
624         //QDomElement infoXml_old = infoXmlNode.toElement();
625         if (!infoXml_old.isNull()) {
626             QDomNodeList folders = infoXml_old.elementsByTagName("folder");
627             int fsize = folders.size();
628             int groupId = max_kproducer_id + 1; // Start at +1 of max id of the kdenlive_producers
629             for (int i = 0; i < fsize; ++i) {
630                 QDomElement folder = folders.at(i).toElement();
631                 if (!folder.isNull()) {
632                     QString groupName = folder.attribute("name");
633                     kDebug() << "groupName: " << groupName << " with groupId: " << groupId;
634                     QDomNodeList fproducers = folder.elementsByTagName("producer");
635                     int psize = fproducers.size();
636                     for (int j = 0; j < psize; ++j) {
637                         QDomElement fproducer = fproducers.at(j).toElement();
638                         if (!fproducer.isNull()) {
639                             QString id = fproducer.attribute("id");
640                             // This is not very effective, but compared to loading the clips, its a breeze
641                             QDomNodeList kdenlive_producers = infoXml_new.elementsByTagName("kdenlive_producer");
642                             int kpsize = kdenlive_producers.size();
643                             for (int k = 0; k < kpsize; ++k) {
644                                 QDomElement kproducer = kdenlive_producers.at(k).toElement(); // Its an element for sure
645                                 if (id == kproducer.attribute("id")) {
646                                     // We do not check that it already is part of a folder
647                                     kproducer.setAttribute("groupid", groupId);
648                                     kproducer.setAttribute("groupname", groupName);
649                                     break;
650                                 }
651                             }
652                         }
653                     }
654                     ++groupId;
655                 }
656             }
657         }
658 #endif
659         QDomNodeList elements = westley.childNodes();
660         max = elements.count();
661         for (int i = 0; i < max; i++) {
662             QDomElement prod = elements.at(0).toElement();
663             westley0.insertAfter(prod, QDomNode());
664         }
665
666         westley0.appendChild(infoXml_new);
667
668         westley0.removeChild(westley);
669
670         // adds <avfile /> information to <kdenlive_producer />
671         QDomNodeList kproducers = m_doc.elementsByTagName("kdenlive_producer");
672         QDomNodeList avfiles = infoXml_old.elementsByTagName("avfile");
673         kDebug() << "found" << avfiles.count() << "<avfile />s and" << kproducers.count() << "<kdenlive_producer />s";
674         for (int i = 0; i < avfiles.count(); ++i) {
675             QDomElement avfile = avfiles.at(i).toElement();
676             QDomElement kproducer;
677             if (avfile.isNull())
678                 kWarning() << "found an <avfile /> that is not a QDomElement";
679             else {
680                 QString id = avfile.attribute("id");
681                 // this is horrible, must be rewritten, it's just for test
682                 for (int j = 0; j < kproducers.count(); ++j) {
683                     //kDebug() << "checking <kdenlive_producer /> with id" << kproducers.at(j).toElement().attribute("id");
684                     if (kproducers.at(j).toElement().attribute("id") == id) {
685                         kproducer = kproducers.at(j).toElement();
686                         break;
687                     }
688                 }
689                 if (kproducer == QDomElement())
690                     kWarning() << "no match for <avfile /> with id =" << id;
691                 else {
692                     //kDebug() << "ready to set additional <avfile />'s attributes (id =" << id << ")";
693                     kproducer.setAttribute("channels", avfile.attribute("channels"));
694                     kproducer.setAttribute("duration", avfile.attribute("duration"));
695                     kproducer.setAttribute("frame_size", avfile.attribute("width") + 'x' + avfile.attribute("height"));
696                     kproducer.setAttribute("frequency", avfile.attribute("frequency"));
697                     if (kproducer.attribute("description").isEmpty() && !avfile.attribute("description").isEmpty())
698                         kproducer.setAttribute("description", avfile.attribute("description"));
699                 }
700             }
701         }
702         infoXml = infoXml_new;
703     }
704
705     if (version <= 0.81) {
706         // Add the tracks information
707         QString tracksOrder = infoXml.attribute("tracks");
708         if (tracksOrder.isEmpty()) {
709             QDomNodeList tracks = m_doc.elementsByTagName("track");
710             for (int i = 0; i < tracks.count(); i++) {
711                 QDomElement track = tracks.at(i).toElement();
712                 if (track.attribute("producer") != "black_track") {
713                     if (track.attribute("hide") == "video")
714                         tracksOrder.append('a');
715                     else
716                         tracksOrder.append('v');
717                 }
718             }
719         }
720         QDomElement tracksinfo = m_doc.createElement("tracksinfo");
721         for (int i = 0; i < tracksOrder.size(); i++) {
722             QDomElement trackinfo = m_doc.createElement("trackinfo");
723             if (tracksOrder.data()[i] == 'a') {
724                 trackinfo.setAttribute("type", "audio");
725                 trackinfo.setAttribute("blind", true);
726             } else
727                 trackinfo.setAttribute("blind", false);
728             trackinfo.setAttribute("mute", false);
729             trackinfo.setAttribute("locked", false);
730             tracksinfo.appendChild(trackinfo);
731         }
732         infoXml.appendChild(tracksinfo);
733     }
734
735     if (version <= 0.82) {
736         // Convert <westley />s in <mlt />s (MLT extreme makeover)
737         QDomNodeList westleyNodes = m_doc.elementsByTagName("westley");
738         for (int i = 0; i < westleyNodes.count(); i++) {
739             QDomElement westley = westleyNodes.at(i).toElement();
740             westley.setTagName("mlt");
741         }
742     }
743
744     if (version <= 0.83) {
745         // Replace point size with pixel size in text titles
746         if (m_doc.toString().contains("font-size")) {
747             KMessageBox::ButtonCode convert = KMessageBox::Continue;
748             QDomNodeList kproducerNodes = m_doc.elementsByTagName("kdenlive_producer");
749             for (int i = 0; i < kproducerNodes.count() && convert != KMessageBox::No; ++i) {
750                 QDomElement kproducer = kproducerNodes.at(i).toElement();
751                 if (kproducer.attribute("type").toInt() == TEXT) {
752                     QDomDocument data;
753                     data.setContent(kproducer.attribute("xmldata"));
754                     QDomNodeList items = data.firstChild().childNodes();
755                     for (int j = 0; j < items.count() && convert != KMessageBox::No; ++j) {
756                         if (items.at(j).attributes().namedItem("type").nodeValue() == "QGraphicsTextItem") {
757                             QDomNamedNodeMap textProperties = items.at(j).namedItem("content").attributes();
758                             if (textProperties.namedItem("font-pixel-size").isNull() && !textProperties.namedItem("font-size").isNull()) {
759                                 // Ask the user if he wants to convert
760                                 if (convert != KMessageBox::Yes && convert != KMessageBox::No)
761                                     convert = (KMessageBox::ButtonCode)KMessageBox::warningYesNo(kapp->activeWindow(), i18n("Some of your text clips were saved with size in points, which means different sizes on different displays. Do you want to convert them to pixel size, making them portable? It is recommended you do this on the computer they were first created on, or you could have to adjust their size."), i18n("Update Text Clips"));
762                                 if (convert == KMessageBox::Yes) {
763                                     QFont font;
764                                     font.setPointSize(textProperties.namedItem("font-size").nodeValue().toInt());
765                                     QDomElement content = items.at(j).namedItem("content").toElement();
766                                     content.setAttribute("font-pixel-size", QFontInfo(font).pixelSize());
767                                     content.removeAttribute("font-size");
768                                     kproducer.setAttribute("xmldata", data.toString());
769                                     /*
770                                      * You may be tempted to delete the preview file
771                                      * to force its recreation: bad idea (see
772                                      * http://www.kdenlive.org/mantis/view.php?id=749)
773                                      */
774                                 }
775                             }
776                         }
777                     }
778                 }
779             }
780         }
781
782         // Fill the <documentproperties /> element
783         QDomElement docProperties = infoXml.firstChildElement("documentproperties");
784         if (docProperties.isNull()) {
785             docProperties = m_doc.createElement("documentproperties");
786             docProperties.setAttribute("zonein", infoXml.attribute("zonein"));
787             docProperties.setAttribute("zoneout", infoXml.attribute("zoneout"));
788             docProperties.setAttribute("zoom", infoXml.attribute("zoom"));
789             docProperties.setAttribute("position", infoXml.attribute("position"));
790             infoXml.appendChild(docProperties);
791         }
792     }
793
794     if (version <= 0.84) {
795         // update the title clips to use the new MLT kdenlivetitle producer
796         QDomNodeList kproducerNodes = m_doc.elementsByTagName("kdenlive_producer");
797         for (int i = 0; i < kproducerNodes.count(); ++i) {
798             QDomElement kproducer = kproducerNodes.at(i).toElement();
799             if (kproducer.attribute("type").toInt() == TEXT) {
800                 QString data = kproducer.attribute("xmldata");
801                 QString datafile = kproducer.attribute("resource");
802                 if (!datafile.endsWith(".kdenlivetitle")) {
803                     datafile = QString();
804                     kproducer.setAttribute("resource", QString());
805                 }
806                 QString id = kproducer.attribute("id");
807                 QDomNodeList mltproducers = m_doc.elementsByTagName("producer");
808                 bool foundData = false;
809                 bool foundResource = false;
810                 bool foundService = false;
811                 for (int j = 0; j < mltproducers.count(); j++) {
812                     QDomElement wproducer = mltproducers.at(j).toElement();
813                     if (wproducer.attribute("id") == id) {
814                         QDomNodeList props = wproducer.childNodes();
815                         for (int k = 0; k < props.count(); k++) {
816                             if (props.at(k).toElement().attribute("name") == "xmldata") {
817                                 props.at(k).firstChild().setNodeValue(data);
818                                 foundData = true;
819                             } else if (props.at(k).toElement().attribute("name") == "mlt_service") {
820                                 props.at(k).firstChild().setNodeValue("kdenlivetitle");
821                                 foundService = true;
822                             } else if (props.at(k).toElement().attribute("name") == "resource") {
823                                 props.at(k).firstChild().setNodeValue(datafile);
824                                 foundResource = true;
825                             }
826                         }
827                         if (!foundData) {
828                             QDomElement e = m_doc.createElement("property");
829                             e.setAttribute("name", "xmldata");
830                             QDomText value = m_doc.createTextNode(data);
831                             e.appendChild(value);
832                             wproducer.appendChild(e);
833                         }
834                         if (!foundService) {
835                             QDomElement e = m_doc.createElement("property");
836                             e.setAttribute("name", "mlt_service");
837                             QDomText value = m_doc.createTextNode("kdenlivetitle");
838                             e.appendChild(value);
839                             wproducer.appendChild(e);
840                         }
841                         if (!foundResource) {
842                             QDomElement e = m_doc.createElement("property");
843                             e.setAttribute("name", "resource");
844                             QDomText value = m_doc.createTextNode(datafile);
845                             e.appendChild(value);
846                             wproducer.appendChild(e);
847                         }
848                         break;
849                     }
850                 }
851             }
852         }
853     }
854     if (version <= 0.85) {
855         // update the LADSPA effects to use the new ladspa.id format instead of external xml file
856         QDomNodeList effectNodes = m_doc.elementsByTagName("filter");
857         for (int i = 0; i < effectNodes.count(); ++i) {
858             QDomElement effect = effectNodes.at(i).toElement();
859             if (EffectsList::property(effect, "mlt_service") == "ladspa") {
860                 // Needs to be converted
861                 QStringList info = getInfoFromEffectName(EffectsList::property(effect, "kdenlive_id"));
862                 if (info.isEmpty()) continue;
863                 // info contains the correct ladspa.id from kdenlive effect name, and a list of parameter's old and new names
864                 EffectsList::setProperty(effect, "kdenlive_id", info.at(0));
865                 EffectsList::setProperty(effect, "tag", info.at(0));
866                 EffectsList::setProperty(effect, "mlt_service", info.at(0));
867                 EffectsList::removeProperty(effect, "src");
868                 for (int j = 1; j < info.size(); j++) {
869                     QString value = EffectsList::property(effect, info.at(j).section('=', 0, 0));
870                     if (!value.isEmpty()) {
871                         // update parameter name
872                         EffectsList::renameProperty(effect, info.at(j).section('=', 0, 0), info.at(j).section('=', 1, 1));
873                     }
874                 }
875             }
876         }
877     }
878
879     if (version <= 0.86) {
880         // Make sure we don't have avformat-novalidate producers, since it caused crashes
881         QDomNodeList producers = m_doc.elementsByTagName("producer");
882         int max = producers.count();
883         for (int i = 0; i < max; i++) {
884             QDomElement prod = producers.at(i).toElement();
885             if (EffectsList::property(prod, "mlt_service") == "avformat-novalidate")
886                 EffectsList::setProperty(prod, "mlt_service", "avformat");
887         }
888
889         // There was a mistake in Geometry transitions where the last keyframe was created one frame after the end of transition, so fix it and move last keyframe to real end of transition
890
891         // Get profile info (width / height)
892         int profileWidth;
893         int profileHeight;
894         QDomElement profile = m_doc.firstChildElement("profile");
895         if (profile.isNull()) profile = infoXml.firstChildElement("profileinfo");
896         if (profile.isNull()) {
897             // could not find profile info, set PAL
898             profileWidth = 720;
899             profileHeight = 576;
900         }
901         else {
902             profileWidth = profile.attribute("width").toInt();
903             profileHeight = profile.attribute("height").toInt();
904         }
905         QDomNodeList transitions = m_doc.elementsByTagName("transition");
906         max = transitions.count();
907         int out;
908         for (int i = 0; i < max; i++) {
909             QDomElement trans = transitions.at(i).toElement();
910             out = trans.attribute("out").toInt() - trans.attribute("in").toInt();
911             QString geom = EffectsList::property(trans, "geometry");
912             Mlt::Geometry *g = new Mlt::Geometry(geom.toUtf8().data(), out, profileWidth, profileHeight);
913             Mlt::GeometryItem item;
914             if (g->next_key(&item, out) == 0) {
915                 // We have a keyframe just after last frame, try to move it to last frame
916                 if (item.frame() == out + 1) {
917                     item.frame(out);
918                     g->insert(item);
919                     g->remove(out + 1);
920                     EffectsList::setProperty(trans, "geometry", g->serialise());
921                 }
922             }
923             delete g;
924         }
925     }
926
927     if (version <= 0.87) {
928         if (!m_doc.firstChildElement("mlt").hasAttribute("LC_NUMERIC")) {
929             m_doc.firstChildElement("mlt").setAttribute("LC_NUMERIC", "C");
930         }
931     }
932
933     // The document has been converted: mark it as modified
934     infoXml.setAttribute("version", currentVersion);
935     m_modified = true;
936     return true;
937 }
938
939 QStringList DocumentValidator::getInfoFromEffectName(const QString oldName)
940 {
941     QStringList info;
942     // Returns a list to convert old Kdenlive ladspa effects
943     if (oldName == "pitch_shift") {
944         info << "ladspa.1433";
945         info << "pitch=0";
946     }
947     else if (oldName == "vinyl") {
948         info << "ladspa.1905";
949         info << "year=0";
950         info << "rpm=1";
951         info << "warping=2";
952         info << "crackle=3";
953         info << "wear=4";
954     }
955     else if (oldName == "room_reverb") {
956         info << "ladspa.1216";
957         info << "room=0";
958         info << "delay=1";
959         info << "damp=2";
960     }
961     else if (oldName == "reverb") {
962         info << "ladspa.1423";
963         info << "room=0";
964         info << "damp=1";
965     }
966     else if (oldName == "rate_scale") {
967         info << "ladspa.1417";
968         info << "rate=0";
969     }
970     else if (oldName == "pitch_scale") {
971         info << "ladspa.1193";
972         info << "coef=0";
973     }
974     else if (oldName == "phaser") {
975         info << "ladspa.1217";
976         info << "rate=0";
977         info << "depth=1";
978         info << "feedback=2";
979         info << "spread=3";
980     }
981     else if (oldName == "limiter") {
982         info << "ladspa.1913";
983         info << "gain=0";
984         info << "limit=1";
985         info << "release=2";
986     }
987     else if (oldName == "equalizer_15") {
988         info << "ladspa.1197";
989         info << "1=0";
990         info << "2=1";
991         info << "3=2";
992         info << "4=3";
993         info << "5=4";
994         info << "6=5";
995         info << "7=6";
996         info << "8=7";
997         info << "9=8";
998         info << "10=9";
999         info << "11=10";
1000         info << "12=11";
1001         info << "13=12";
1002         info << "14=13";
1003         info << "15=14";
1004     }
1005     else if (oldName == "equalizer") {
1006         info << "ladspa.1901";
1007         info << "logain=0";
1008         info << "midgain=1";
1009         info << "higain=2";
1010     }
1011     else if (oldName == "declipper") {
1012         info << "ladspa.1195";
1013     }
1014     return info;
1015 }
1016
1017 QString DocumentValidator::colorToString(const QColor& c)
1018 {
1019     QString ret = "%1,%2,%3,%4";
1020     ret = ret.arg(c.red()).arg(c.green()).arg(c.blue()).arg(c.alpha());
1021     return ret;
1022 }
1023
1024 bool DocumentValidator::isProject() const
1025 {
1026     QDomNode infoXmlNode = m_doc.elementsByTagName("kdenlivedoc").at(0);
1027     return !infoXmlNode.isNull();
1028 }
1029
1030 bool DocumentValidator::isModified() const
1031 {
1032     return m_modified;
1033 }
1034
1035 void DocumentValidator::updateEffects()
1036 {
1037     // WARNING: order by findDirs will determine which js file to use (in case multiple scripts for the same filter exist)
1038     QMap <QString, KUrl> paths;
1039 #if QT_VERSION >= 0x040700
1040     QMap <QString, QScriptProgram> scripts;
1041 #else
1042     QMap <QString, QString> scripts;
1043 #endif
1044     QStringList directories = KGlobal::dirs()->findDirs("appdata", "effects/update");
1045     foreach (const QString &directoryName, directories) {
1046         QDir directory(directoryName);
1047         QStringList fileList = directory.entryList(QStringList() << "*.js", QDir::Files);
1048         foreach (const QString &fileName, fileList) {
1049             QString identifier = fileName;
1050             // remove extension (".js")
1051             identifier.chop(3);
1052             paths.insert(identifier, KUrl(directoryName + fileName));
1053         }
1054     }
1055
1056     QDomNodeList effects = m_doc.elementsByTagName("filter");
1057     int max = effects.count();
1058     QStringList safeEffects;
1059     for(int i = 0; i < max; ++i) {
1060         QDomElement effect = effects.at(i).toElement();
1061         QString effectId = EffectsList::property(effect, "kdenlive_id");
1062         if (safeEffects.contains(effectId)) {
1063             // Do not check the same effect twice if it is at the correct version
1064             // (assume we don't have different versions of the same effect in a project file)
1065             continue;
1066         }
1067         QString effectTag = EffectsList::property(effect, "tag");
1068         QString effectVersionStr = EffectsList::property(effect, "version");
1069         double effectVersion = effectVersionStr.isNull() ? -1 : effectVersionStr.toDouble();
1070
1071         QDomElement effectDescr = MainWindow::customEffects.getEffectByTag(QString(), effectId);
1072         if (effectDescr.isNull()) {
1073             effectDescr = MainWindow::videoEffects.getEffectByTag(effectTag, effectId);
1074         }
1075         if (effectDescr.isNull()) {
1076             effectDescr = MainWindow::audioEffects.getEffectByTag(effectTag, effectId);
1077         }
1078         if (!effectDescr.isNull()) {
1079             double serviceVersion = -1;
1080             QDomElement serviceVersionElem = effectDescr.firstChildElement("version");
1081             if (!serviceVersionElem.isNull()) {
1082                 serviceVersion = serviceVersionElem.text().toDouble();
1083             }
1084             if (serviceVersion != effectVersion && paths.contains(effectId)) {
1085                 if (!scripts.contains(effectId)) {
1086                     QFile scriptFile(paths.value(effectId).path());
1087                     if (!scriptFile.open(QIODevice::ReadOnly)) {
1088                         continue;
1089                     }
1090 #if QT_VERSION >= 0x040700
1091                     QScriptProgram scriptProgram(scriptFile.readAll());
1092 #else
1093                     QString scriptProgram = scriptFile.readAll();
1094 #endif
1095                     scriptFile.close();
1096                     scripts.insert(effectId, scriptProgram);
1097                 }
1098
1099                 QScriptEngine scriptEngine;
1100                 scriptEngine.importExtension("qt.core");
1101                 scriptEngine.importExtension("qt.xml");
1102                 scriptEngine.evaluate(scripts.value(effectId));
1103                 QScriptValue updateRules = scriptEngine.globalObject().property("update");
1104                 if (!updateRules.isValid())
1105                     continue;
1106                 if (updateRules.isFunction()) {
1107                     QDomDocument scriptDoc;
1108                     scriptDoc.appendChild(scriptDoc.importNode(effect, true));
1109
1110                     QString effectString = updateRules.call(QScriptValue(), QScriptValueList()  << serviceVersion << effectVersion << scriptDoc.toString()).toString();
1111
1112                     if (!effectString.isEmpty() && !scriptEngine.hasUncaughtException()) {
1113                         scriptDoc.setContent(effectString);
1114                         QDomNode updatedEffect = effect.ownerDocument().importNode(scriptDoc.documentElement(), true);
1115                         effect.parentNode().replaceChild(updatedEffect, effect);
1116                         m_modified = true;
1117                     }
1118                 } else {
1119                     m_modified = updateEffectParameters(effect.childNodes(), &updateRules, serviceVersion, effectVersion);
1120                 }
1121
1122                 // set version number since MLT won't change it (only initially set it)
1123                 QDomElement versionElem = effect.firstChildElement("version");
1124                 if (EffectsList::property(effect, "version").isNull()) {
1125                     versionElem = effect.ownerDocument().createTextNode(QLocale().toString(serviceVersion)).toElement();
1126                     versionElem.setTagName("property");
1127                     versionElem.setAttribute("name", "version");
1128                     effect.appendChild(versionElem);
1129                 } else {
1130                     EffectsList::setProperty(effect, "version", QLocale().toString(serviceVersion));
1131                 }
1132             }
1133             else safeEffects.append(effectId);
1134         }
1135     }
1136 }
1137
1138 bool DocumentValidator::updateEffectParameters(QDomNodeList parameters, const QScriptValue* updateRules, const double serviceVersion, const double effectVersion)
1139 {
1140     bool updated = false;
1141     bool isDowngrade = serviceVersion < effectVersion;
1142     for (int i = 0; i < parameters.count(); ++i) {
1143         QDomElement parameter = parameters.at(i).toElement();
1144         QScriptValue rules = updateRules->property(parameter.attribute("name"));
1145         if (rules.isValid() && rules.isArray()) {
1146             int rulesCount = rules.property("length").toInt32();
1147             if (isDowngrade) {
1148                 // start with the highest version and downgrade step by step
1149                 for (int j = rulesCount - 1; j >= 0; --j) {
1150                     double version = rules.property(j).property(0).toNumber();
1151                     if (version <= effectVersion && version > serviceVersion) {
1152                         parameter.firstChild().setNodeValue(rules.property(j).property(1).call(QScriptValue(), QScriptValueList() << parameter.text() << isDowngrade).toString());
1153                         updated = true;
1154                     }
1155                 }
1156             } else {
1157                 for (int j = 0; j < rulesCount; ++j) {
1158                     double version = rules.property(j).property(0).toNumber();
1159                     if (version > effectVersion && version <= serviceVersion) {
1160                         parameter.firstChild().setNodeValue(rules.property(j).property(1).call(QScriptValue(), QScriptValueList() << parameter.text() << isDowngrade).toString());
1161                         updated = true;
1162                     }
1163                 }
1164             }
1165         }
1166     }
1167     return updated;
1168 }