kdelibs/kdeui/xmlgui/kxmlguiversionhandler.cpp
Ivailo Monev 871151e83c generic: remove QT_KATIE definition checks
only Katie is supported now

Signed-off-by: Ivailo Monev <xakepa10@gmail.com>
2023-06-08 14:38:36 +03:00

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;
}
}