mirror of
https://bitbucket.org/smil3y/kdelibs.git
synced 2025-02-23 18:32:49 +00:00
344 lines
12 KiB
C++
344 lines
12 KiB
C++
/* This file is part of the KDE libraries
|
|
Copyright (C) 2000 Simon Hausmann <hausmann@kde.org>
|
|
Copyright (C) 2000 Kurt Granroth <granroth@kde.org>
|
|
Copyright 2007 David Faure <faure@kde.org>
|
|
|
|
This library is free software; you can redistribute it and/or
|
|
modify it under the terms of the GNU Library General Public
|
|
License as published by the Free Software Foundation; either
|
|
version 2 of the License, or (at your option) any later version.
|
|
|
|
This library is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
Library General Public License for more details.
|
|
|
|
You should have received a copy of the GNU Library General Public License
|
|
along with this library; see the file COPYING.LIB. If not, write to
|
|
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
|
|
Boston, MA 02110-1301, USA.
|
|
*/
|
|
|
|
#include "kxmlguiversionhandler_p.h"
|
|
#include <kdebug.h>
|
|
#include <QFile>
|
|
#include <QtXml/qdom.h>
|
|
#include "kxmlguifactory.h"
|
|
#include <kglobal.h>
|
|
#include <kstandarddirs.h>
|
|
#include <QtXml/qdom.h>
|
|
|
|
struct DocStruct
|
|
{
|
|
QString file;
|
|
QString data;
|
|
};
|
|
|
|
static QList<QDomElement> extractToolBars(const QDomDocument& doc)
|
|
{
|
|
QList<QDomElement> toolbars;
|
|
QDomElement parent = doc.documentElement();
|
|
for (QDomElement e = parent.firstChildElement(); !e.isNull(); e = e.nextSiblingElement()) {
|
|
if (e.tagName().compare(QLatin1String("ToolBar"), Qt::CaseInsensitive) == 0) {
|
|
toolbars.append(e);
|
|
}
|
|
}
|
|
return toolbars;
|
|
}
|
|
|
|
static void removeAllToolBars(QDomDocument& doc)
|
|
{
|
|
QDomElement parent = doc.documentElement();
|
|
const QList<QDomElement> toolBars = extractToolBars(doc);
|
|
foreach(const QDomElement& e, toolBars) {
|
|
parent.removeChild(e);
|
|
}
|
|
}
|
|
|
|
static void insertToolBars(QDomDocument& doc, const QList<QDomElement>& toolBars)
|
|
{
|
|
QDomElement parent = doc.documentElement();
|
|
QDomElement menuBar = parent.namedItem("MenuBar").toElement();
|
|
QDomElement insertAfter = menuBar;
|
|
if (menuBar.isNull())
|
|
insertAfter = parent.firstChildElement(); // if null, insertAfter will do an append
|
|
foreach(const QDomElement& e, toolBars) {
|
|
QDomNode result = parent.insertAfter(e, insertAfter);
|
|
Q_ASSERT(!result.isNull());
|
|
}
|
|
}
|
|
|
|
//
|
|
|
|
typedef QMap<QString, QMap<QString, QString> > ActionPropertiesMap;
|
|
|
|
static ActionPropertiesMap extractActionProperties(const QDomDocument &doc)
|
|
{
|
|
ActionPropertiesMap properties;
|
|
|
|
QDomElement actionPropElement = doc.documentElement().namedItem( "ActionProperties" ).toElement();
|
|
|
|
if ( actionPropElement.isNull() )
|
|
return properties;
|
|
|
|
QDomNode n = actionPropElement.firstChild();
|
|
while(!n.isNull())
|
|
{
|
|
QDomElement e = n.toElement();
|
|
n = n.nextSibling(); // Advance now so that we can safely delete e
|
|
if ( e.isNull() )
|
|
continue;
|
|
|
|
if ( e.tagName().compare("action", Qt::CaseInsensitive) != 0 )
|
|
continue;
|
|
|
|
const QString actionName = e.attribute( "name" );
|
|
if ( actionName.isEmpty() )
|
|
continue;
|
|
|
|
QMap<QString, QMap<QString, QString> >::Iterator propIt = properties.find( actionName );
|
|
if ( propIt == properties.end() )
|
|
propIt = properties.insert( actionName, QMap<QString, QString>() );
|
|
|
|
const QDomNamedNodeMap attributes = e.attributes();
|
|
|
|
for (int i = 0; i < attributes.length(); i++ )
|
|
{
|
|
const QDomAttr attr = attributes.item( i ).toAttr();
|
|
|
|
if ( attr.isNull() )
|
|
continue;
|
|
|
|
const QString name = attr.name();
|
|
|
|
if ( name == "name" || name.isEmpty() )
|
|
continue;
|
|
|
|
(*propIt)[ name ] = attr.value();
|
|
}
|
|
|
|
}
|
|
|
|
return properties;
|
|
}
|
|
|
|
static void storeActionProperties( QDomDocument &doc,
|
|
const ActionPropertiesMap &properties )
|
|
{
|
|
QDomElement actionPropElement = doc.documentElement().namedItem( "ActionProperties" ).toElement();
|
|
|
|
if ( actionPropElement.isNull() )
|
|
{
|
|
actionPropElement = doc.createElement( "ActionProperties" );
|
|
doc.documentElement().appendChild( actionPropElement );
|
|
}
|
|
|
|
//Remove only those ActionProperties entries from the document, that are present
|
|
//in the properties argument. In real life this means that local ActionProperties
|
|
//takes precedence over global ones, if they exists (think local override of shortcuts).
|
|
QDomNode actionNode = actionPropElement.firstChild();
|
|
while (!actionNode.isNull())
|
|
{
|
|
if (properties.contains(actionNode.toElement().attribute("name")))
|
|
{
|
|
QDomNode nextNode = actionNode.nextSibling();
|
|
actionPropElement.removeChild( actionNode );
|
|
actionNode = nextNode;
|
|
} else
|
|
actionNode = actionNode.nextSibling();
|
|
}
|
|
|
|
ActionPropertiesMap::ConstIterator it = properties.begin();
|
|
const ActionPropertiesMap::ConstIterator end = properties.end();
|
|
for (; it != end; ++it )
|
|
{
|
|
QDomElement action = doc.createElement( "Action" );
|
|
action.setAttribute( "name", it.key() );
|
|
actionPropElement.appendChild( action );
|
|
|
|
const QMap<QString, QString> attributes = (*it);
|
|
QMap<QString, QString>::ConstIterator attrIt = attributes.begin();
|
|
const QMap<QString, QString>::ConstIterator attrEnd = attributes.end();
|
|
for (; attrIt != attrEnd; ++attrIt )
|
|
action.setAttribute( attrIt.key(), attrIt.value() );
|
|
}
|
|
}
|
|
|
|
QString KXmlGuiVersionHandler::findVersionNumber( const QString &xml )
|
|
{
|
|
enum { ST_START, ST_AFTER_OPEN, ST_AFTER_GUI,
|
|
ST_EXPECT_VERSION, ST_VERSION_NUM} state = ST_START;
|
|
const int length = xml.length();
|
|
for (int pos = 0; pos < length; pos++) {
|
|
switch (state) {
|
|
case ST_START:
|
|
if (xml[pos] == '<')
|
|
state = ST_AFTER_OPEN;
|
|
break;
|
|
case ST_AFTER_OPEN:
|
|
{
|
|
//Jump to gui..
|
|
const int guipos = xml.indexOf("gui", pos, Qt::CaseInsensitive);
|
|
if (guipos == -1)
|
|
return QString(); //Reject
|
|
|
|
pos = guipos + 2; //Position at i, so we're moved ahead to the next character by the ++;
|
|
state = ST_AFTER_GUI;
|
|
break;
|
|
}
|
|
case ST_AFTER_GUI:
|
|
state = ST_EXPECT_VERSION;
|
|
break;
|
|
case ST_EXPECT_VERSION:
|
|
{
|
|
const int verpos = xml.indexOf("version", pos, Qt::CaseInsensitive);
|
|
if (verpos == -1)
|
|
return QString(); //Reject
|
|
pos = verpos + 7; // strlen("version") is 7
|
|
while (xml.at(pos).isSpace())
|
|
++pos;
|
|
if (xml.at(pos++) != '=')
|
|
return QString(); //Reject
|
|
while (xml.at(pos).isSpace())
|
|
++pos;
|
|
|
|
state = ST_VERSION_NUM;
|
|
break;
|
|
}
|
|
case ST_VERSION_NUM:
|
|
{
|
|
int endpos;
|
|
for (endpos = pos; endpos < length; endpos++) {
|
|
const ushort ch = xml[endpos].unicode();
|
|
if (ch >= '0' && ch <= '9')
|
|
continue; //Number..
|
|
if (ch == '"') //End of parameter
|
|
break;
|
|
else { //This shouldn't be here..
|
|
endpos = length;
|
|
}
|
|
}
|
|
|
|
if (endpos != pos && endpos < length ) {
|
|
const QString matchCandidate = xml.mid(pos, endpos - pos); //Don't include " ".
|
|
return matchCandidate;
|
|
}
|
|
|
|
state = ST_EXPECT_VERSION; //Try to match a well-formed version..
|
|
break;
|
|
} //case..
|
|
} //switch
|
|
} //for
|
|
|
|
return QString();
|
|
}
|
|
|
|
|
|
KXmlGuiVersionHandler::KXmlGuiVersionHandler(const QStringList& files)
|
|
{
|
|
Q_ASSERT(!files.isEmpty());
|
|
|
|
if (files.count() == 1) {
|
|
// No need to parse version numbers if there's only one file anyway
|
|
m_file = files.first();
|
|
m_doc = KXMLGUIFactory::readConfigFile(m_file);
|
|
return;
|
|
}
|
|
|
|
|
|
QList<DocStruct> allDocuments;
|
|
|
|
foreach (const QString &file, files) {
|
|
DocStruct d;
|
|
d.file = file;
|
|
d.data = KXMLGUIFactory::readConfigFile( file );
|
|
allDocuments.append( d );
|
|
}
|
|
|
|
QList<DocStruct>::iterator best = allDocuments.end();
|
|
uint bestVersion = 0;
|
|
|
|
QList<DocStruct>::iterator docIt = allDocuments.begin();
|
|
const QList<DocStruct>::iterator docEnd = allDocuments.end();
|
|
for (; docIt != docEnd; ++docIt ) {
|
|
const QString versionStr = findVersionNumber( (*docIt).data );
|
|
if ( versionStr.isEmpty() ) {
|
|
kDebug(260) << "found no version in" << (*docIt).file;
|
|
continue;
|
|
}
|
|
|
|
bool ok = false;
|
|
uint version = versionStr.toUInt( &ok );
|
|
if ( !ok )
|
|
continue;
|
|
//kDebug(260) << "found version" << version << "for" << (*docIt).file;
|
|
|
|
if ( version > bestVersion ) {
|
|
best = docIt;
|
|
//kDebug(260) << "best version is now " << version;
|
|
bestVersion = version;
|
|
}
|
|
}
|
|
|
|
if ( best != docEnd ) {
|
|
if ( best != allDocuments.begin() ) {
|
|
QList<DocStruct>::iterator local = allDocuments.begin();
|
|
|
|
if ( (*local).file.startsWith(KGlobal::dirs()->localkdedir()) ||
|
|
(*local).file.startsWith(KGlobal::dirs()->saveLocation("appdata")) ) {
|
|
// load the local document and extract the action properties
|
|
QDomDocument localDocument;
|
|
localDocument.setContent( (*local).data );
|
|
|
|
const ActionPropertiesMap properties = extractActionProperties(localDocument);
|
|
const QList<QDomElement> toolbars = extractToolBars(localDocument);
|
|
|
|
// in case the document has a ActionProperties section
|
|
// we must not delete it but copy over the global doc
|
|
// to the local and insert the ActionProperties section
|
|
|
|
// TODO: kedittoolbar should mark toolbars as modified so that
|
|
// we don't keep old toolbars just because the user defined a shortcut
|
|
|
|
if ( !properties.isEmpty() || !toolbars.isEmpty() ) {
|
|
// now load the global one with the higher version number
|
|
// into memory
|
|
QDomDocument document;
|
|
document.setContent( (*best).data );
|
|
// and store the properties in there
|
|
storeActionProperties( document, properties );
|
|
if (!toolbars.isEmpty()) {
|
|
// remove application toolbars
|
|
removeAllToolBars(document);
|
|
// add user toolbars
|
|
insertToolBars(document, toolbars);
|
|
}
|
|
|
|
(*local).data = document.toString();
|
|
// make sure we pick up the new local doc, when we return later
|
|
best = local;
|
|
|
|
// write out the new version of the local document
|
|
QFile f( (*local).file );
|
|
if ( f.open( QIODevice::WriteOnly ) )
|
|
{
|
|
const QByteArray utf8data = (*local).data.toUtf8();
|
|
f.write( utf8data.constData(), utf8data.length() );
|
|
f.close();
|
|
}
|
|
} else {
|
|
// Move away the outdated local file, to speed things up next time
|
|
const QString f = (*local).file;
|
|
const QString backup = f + QLatin1String( ".backup" );
|
|
QFile::rename( f, backup );
|
|
}
|
|
}
|
|
}
|
|
m_doc = (*best).data;
|
|
m_file = (*best).file;
|
|
} else {
|
|
//kDebug(260) << "returning first one...";
|
|
m_doc = allDocuments.first().data;
|
|
m_file = allDocuments.first().file;
|
|
}
|
|
}
|