KDEUI
kxmlguiversionhandler.cpp
Go to the documentation of this file.
00001 /* This file is part of the KDE libraries 00002 Copyright (C) 2000 Simon Hausmann <hausmann@kde.org> 00003 Copyright (C) 2000 Kurt Granroth <granroth@kde.org> 00004 Copyright 2007 David Faure <faure@kde.org> 00005 00006 This library is free software; you can redistribute it and/or 00007 modify it under the terms of the GNU Library General Public 00008 License as published by the Free Software Foundation; either 00009 version 2 of the License, or (at your option) any later version. 00010 00011 This library is distributed in the hope that it will be useful, 00012 but WITHOUT ANY WARRANTY; without even the implied warranty of 00013 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 00014 Library General Public License for more details. 00015 00016 You should have received a copy of the GNU Library General Public License 00017 along with this library; see the file COPYING.LIB. If not, write to 00018 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 00019 Boston, MA 02110-1301, USA. 00020 */ 00021 00022 #include "kxmlguiversionhandler_p.h" 00023 #include <kdebug.h> 00024 #include <QFile> 00025 #include <QDomDocument> 00026 #include "kxmlguifactory.h" 00027 #include <kglobal.h> 00028 #include <kstandarddirs.h> 00029 #include <QtXml/QDomElement> 00030 00031 struct DocStruct 00032 { 00033 QString file; 00034 QString data; 00035 }; 00036 00037 static QList<QDomElement> extractToolBars(const QDomDocument& doc) 00038 { 00039 QList<QDomElement> toolbars; 00040 QDomElement parent = doc.documentElement(); 00041 for (QDomElement e = parent.firstChildElement(); !e.isNull(); e = e.nextSiblingElement()) { 00042 if (e.tagName().compare(QLatin1String("ToolBar"), Qt::CaseInsensitive) == 0) { 00043 toolbars.append(e); 00044 } 00045 } 00046 return toolbars; 00047 } 00048 00049 static void removeAllToolBars(QDomDocument& doc) 00050 { 00051 QDomElement parent = doc.documentElement(); 00052 const QList<QDomElement> toolBars = extractToolBars(doc); 00053 foreach(const QDomElement& e, toolBars) { 00054 parent.removeChild(e); 00055 } 00056 } 00057 00058 static void insertToolBars(QDomDocument& doc, const QList<QDomElement>& toolBars) 00059 { 00060 QDomElement parent = doc.documentElement(); 00061 QDomElement menuBar = parent.namedItem("MenuBar").toElement(); 00062 QDomElement insertAfter = menuBar; 00063 if (menuBar.isNull()) 00064 insertAfter = parent.firstChildElement(); // if null, insertAfter will do an append 00065 foreach(const QDomElement& e, toolBars) { 00066 QDomNode result = parent.insertAfter(e, insertAfter); 00067 Q_ASSERT(!result.isNull()); 00068 } 00069 } 00070 00071 // 00072 00073 typedef QMap<QString, QMap<QString, QString> > ActionPropertiesMap; 00074 00075 static ActionPropertiesMap extractActionProperties(const QDomDocument &doc) 00076 { 00077 ActionPropertiesMap properties; 00078 00079 QDomElement actionPropElement = doc.documentElement().namedItem( "ActionProperties" ).toElement(); 00080 00081 if ( actionPropElement.isNull() ) 00082 return properties; 00083 00084 QDomNode n = actionPropElement.firstChild(); 00085 while(!n.isNull()) 00086 { 00087 QDomElement e = n.toElement(); 00088 n = n.nextSibling(); // Advance now so that we can safely delete e 00089 if ( e.isNull() ) 00090 continue; 00091 00092 if ( e.tagName().compare("action", Qt::CaseInsensitive) != 0 ) 00093 continue; 00094 00095 const QString actionName = e.attribute( "name" ); 00096 if ( actionName.isEmpty() ) 00097 continue; 00098 00099 QMap<QString, QMap<QString, QString> >::Iterator propIt = properties.find( actionName ); 00100 if ( propIt == properties.end() ) 00101 propIt = properties.insert( actionName, QMap<QString, QString>() ); 00102 00103 const QDomNamedNodeMap attributes = e.attributes(); 00104 const uint attributeslength = attributes.length(); 00105 00106 for ( uint i = 0; i < attributeslength; ++i ) 00107 { 00108 const QDomAttr attr = attributes.item( i ).toAttr(); 00109 00110 if ( attr.isNull() ) 00111 continue; 00112 00113 const QString name = attr.name(); 00114 00115 if ( name == "name" || name.isEmpty() ) 00116 continue; 00117 00118 (*propIt)[ name ] = attr.value(); 00119 } 00120 00121 } 00122 00123 return properties; 00124 } 00125 00126 static void storeActionProperties( QDomDocument &doc, 00127 const ActionPropertiesMap &properties ) 00128 { 00129 QDomElement actionPropElement = doc.documentElement().namedItem( "ActionProperties" ).toElement(); 00130 00131 if ( actionPropElement.isNull() ) 00132 { 00133 actionPropElement = doc.createElement( "ActionProperties" ); 00134 doc.documentElement().appendChild( actionPropElement ); 00135 } 00136 00137 //Remove only those ActionProperties entries from the document, that are present 00138 //in the properties argument. In real life this means that local ActionProperties 00139 //takes precedence over global ones, if they exists (think local override of shortcuts). 00140 QDomNode actionNode = actionPropElement.firstChild(); 00141 while (!actionNode.isNull()) 00142 { 00143 if (properties.contains(actionNode.toElement().attribute("name"))) 00144 { 00145 QDomNode nextNode = actionNode.nextSibling(); 00146 actionPropElement.removeChild( actionNode ); 00147 actionNode = nextNode; 00148 } else 00149 actionNode = actionNode.nextSibling(); 00150 } 00151 00152 ActionPropertiesMap::ConstIterator it = properties.begin(); 00153 const ActionPropertiesMap::ConstIterator end = properties.end(); 00154 for (; it != end; ++it ) 00155 { 00156 QDomElement action = doc.createElement( "Action" ); 00157 action.setAttribute( "name", it.key() ); 00158 actionPropElement.appendChild( action ); 00159 00160 const QMap<QString, QString> attributes = (*it); 00161 QMap<QString, QString>::ConstIterator attrIt = attributes.begin(); 00162 const QMap<QString, QString>::ConstIterator attrEnd = attributes.end(); 00163 for (; attrIt != attrEnd; ++attrIt ) 00164 action.setAttribute( attrIt.key(), attrIt.value() ); 00165 } 00166 } 00167 00168 QString KXmlGuiVersionHandler::findVersionNumber( const QString &xml ) 00169 { 00170 enum { ST_START, ST_AFTER_OPEN, ST_AFTER_GUI, 00171 ST_EXPECT_VERSION, ST_VERSION_NUM} state = ST_START; 00172 const int length = xml.length(); 00173 for (int pos = 0; pos < length; pos++) { 00174 switch (state) { 00175 case ST_START: 00176 if (xml[pos] == '<') 00177 state = ST_AFTER_OPEN; 00178 break; 00179 case ST_AFTER_OPEN: 00180 { 00181 //Jump to gui.. 00182 const int guipos = xml.indexOf("gui", pos, Qt::CaseInsensitive); 00183 if (guipos == -1) 00184 return QString(); //Reject 00185 00186 pos = guipos + 2; //Position at i, so we're moved ahead to the next character by the ++; 00187 state = ST_AFTER_GUI; 00188 break; 00189 } 00190 case ST_AFTER_GUI: 00191 state = ST_EXPECT_VERSION; 00192 break; 00193 case ST_EXPECT_VERSION: 00194 { 00195 const int verpos = xml.indexOf("version", pos, Qt::CaseInsensitive); 00196 if (verpos == -1) 00197 return QString(); //Reject 00198 pos = verpos + 7; // strlen("version") is 7 00199 while (xml.at(pos).isSpace()) 00200 ++pos; 00201 if (xml.at(pos++) != '=') 00202 return QString(); //Reject 00203 while (xml.at(pos).isSpace()) 00204 ++pos; 00205 00206 state = ST_VERSION_NUM; 00207 break; 00208 } 00209 case ST_VERSION_NUM: 00210 { 00211 int endpos; 00212 for (endpos = pos; endpos < length; endpos++) { 00213 const ushort ch = xml[endpos].unicode(); 00214 if (ch >= '0' && ch <= '9') 00215 continue; //Number.. 00216 if (ch == '"') //End of parameter 00217 break; 00218 else { //This shouldn't be here.. 00219 endpos = length; 00220 } 00221 } 00222 00223 if (endpos != pos && endpos < length ) { 00224 const QString matchCandidate = xml.mid(pos, endpos - pos); //Don't include " ". 00225 return matchCandidate; 00226 } 00227 00228 state = ST_EXPECT_VERSION; //Try to match a well-formed version.. 00229 break; 00230 } //case.. 00231 } //switch 00232 } //for 00233 00234 return QString(); 00235 } 00236 00237 00238 KXmlGuiVersionHandler::KXmlGuiVersionHandler(const QStringList& files) 00239 { 00240 Q_ASSERT(!files.isEmpty()); 00241 00242 if (files.count() == 1) { 00243 // No need to parse version numbers if there's only one file anyway 00244 m_file = files.first(); 00245 m_doc = KXMLGUIFactory::readConfigFile(m_file); 00246 return; 00247 } 00248 00249 00250 QList<DocStruct> allDocuments; 00251 00252 foreach (const QString &file, files) { 00253 DocStruct d; 00254 d.file = file; 00255 d.data = KXMLGUIFactory::readConfigFile( file ); 00256 allDocuments.append( d ); 00257 } 00258 00259 QList<DocStruct>::iterator best = allDocuments.end(); 00260 uint bestVersion = 0; 00261 00262 QList<DocStruct>::iterator docIt = allDocuments.begin(); 00263 const QList<DocStruct>::iterator docEnd = allDocuments.end(); 00264 for (; docIt != docEnd; ++docIt ) { 00265 const QString versionStr = findVersionNumber( (*docIt).data ); 00266 if ( versionStr.isEmpty() ) { 00267 kDebug(260) << "found no version in" << (*docIt).file; 00268 continue; 00269 } 00270 00271 bool ok = false; 00272 uint version = versionStr.toUInt( &ok ); 00273 if ( !ok ) 00274 continue; 00275 //kDebug(260) << "found version" << version << "for" << (*docIt).file; 00276 00277 if ( version > bestVersion ) { 00278 best = docIt; 00279 //kDebug(260) << "best version is now " << version; 00280 bestVersion = version; 00281 } 00282 } 00283 00284 if ( best != docEnd ) { 00285 if ( best != allDocuments.begin() ) { 00286 QList<DocStruct>::iterator local = allDocuments.begin(); 00287 00288 if ( (*local).file.startsWith(KGlobal::dirs()->localkdedir()) || 00289 (*local).file.startsWith(KGlobal::dirs()->saveLocation("appdata")) ) { 00290 // load the local document and extract the action properties 00291 QDomDocument localDocument; 00292 localDocument.setContent( (*local).data ); 00293 00294 const ActionPropertiesMap properties = extractActionProperties(localDocument); 00295 const QList<QDomElement> toolbars = extractToolBars(localDocument); 00296 00297 // in case the document has a ActionProperties section 00298 // we must not delete it but copy over the global doc 00299 // to the local and insert the ActionProperties section 00300 00301 // TODO: kedittoolbar should mark toolbars as modified so that 00302 // we don't keep old toolbars just because the user defined a shortcut 00303 00304 if ( !properties.isEmpty() || !toolbars.isEmpty() ) { 00305 // now load the global one with the higher version number 00306 // into memory 00307 QDomDocument document; 00308 document.setContent( (*best).data ); 00309 // and store the properties in there 00310 storeActionProperties( document, properties ); 00311 if (!toolbars.isEmpty()) { 00312 // remove application toolbars 00313 removeAllToolBars(document); 00314 // add user toolbars 00315 insertToolBars(document, toolbars); 00316 } 00317 00318 (*local).data = document.toString(); 00319 // make sure we pick up the new local doc, when we return later 00320 best = local; 00321 00322 // write out the new version of the local document 00323 QFile f( (*local).file ); 00324 if ( f.open( QIODevice::WriteOnly ) ) 00325 { 00326 const QByteArray utf8data = (*local).data.toUtf8(); 00327 f.write( utf8data.constData(), utf8data.length() ); 00328 f.close(); 00329 } 00330 } else { 00331 // Move away the outdated local file, to speed things up next time 00332 const QString f = (*local).file; 00333 const QString backup = f + QLatin1String( ".backup" ); 00334 QFile::rename( f, backup ); 00335 } 00336 } 00337 } 00338 m_doc = (*best).data; 00339 m_file = (*best).file; 00340 } else { 00341 //kDebug(260) << "returning first one..."; 00342 m_doc = allDocuments.first().data; 00343 m_file = allDocuments.first().file; 00344 } 00345 }
KDE 4.6 API Reference