<commit_before>/*
    kopetecontactlist.cpp - Kopete's Contact List backend

    Copyright (c) 2002 by <NAME>       <<EMAIL>>
    Copyright (c) 2002 by <NAME> <<EMAIL>>

    Copyright (c) 2002 by the Kopete developers  <<EMAIL>>

    *************************************************************************
    *                                                                       *
    * This program is free software; you can redistribute it and/or modify  *
    * it under the terms of the GNU General Public License as published by  *
    * the Free Software Foundation; either version 2 of the License, or     *
    * (at your option) any later version.                                   *
    *                                                                       *
    *************************************************************************
*/

#include "kopetecontactlist.h"

#include <qdom.h>
#include <qfile.h>
#include <qregexp.h>
#include <qstylesheet.h>

#include <kapplication.h>
#include <kdebug.h>
#include <kglobal.h>
#include <klocale.h>
#include <ksavefile.h>
#include <kstandarddirs.h>
#include <kmessagebox.h>
#include "kopetemetacontact.h"
#include "kopeteprotocol.h"
#include "pluginloader.h"
#include "kopetegroup.h"


KopeteContactList *KopeteContactList::s_contactList = 0L;

KopeteContactList *KopeteContactList::contactList()
{
	if( !s_contactList )
		s_contactList = new KopeteContactList;

	return s_contactList;
}

KopeteContactList::KopeteContactList()
: QObject( kapp, "KopeteContactList" )
{
}

KopeteContactList::~KopeteContactList()
{
// save is currently called in ~kopete (before the deletion of plugins)
//	save();
}

KopeteMetaContact *KopeteContactList::findContact( const QString &protocolId,
	const QString &identityId, const QString &contactId )
{
	QPtrListIterator<KopeteMetaContact> it( m_contacts );
	for( ; it.current(); ++it )
	{
		if( it.current()->findContact( protocolId, identityId, contactId ) )
			return it.current();
	}
	//kdDebug(14010) << k_funcinfo << "*** Not found!" << endl;
	return 0L;
}

void KopeteContactList::addMetaContact( KopeteMetaContact *mc )
{
	m_contacts.append( mc );

/*	connect( mc,
		SIGNAL( removedFromGroup( KopeteMetaContact *, const QString & ) ),
		SLOT( slotRemovedFromGroup( KopeteMetaContact *, const QString & ) ) );*/

	emit metaContactAdded( mc );
}

/*void KopeteContactList::slotRemovedFromGroup( KopeteMetaContact *mc,
	const QString &  )
{
	if( mc->groups().isEmpty() )
	{
		kdDebug(14010) << "KopeteContactList::slotRemovedFromGroup: "
			<< "contact removed from all groups: now toplevel." << endl;
		//m_contacts.remove( mc );
		//mc->deleteLater();
	}
}                  */

void KopeteContactList::loadXML()
{
	addGroup( KopeteGroup::toplevel );

	QString filename = locateLocal( "appdata", "contactlist.xml" );
	if( filename.isEmpty() )
		return ;

	QDomDocument contactList( "kopete-contact-list" );

	QFile contactListFile( filename );
	contactListFile.open( IO_ReadOnly );
	contactList.setContent( &contactListFile );

	QDomElement list = contactList.documentElement();

	QString versionString = list.attribute( "version", QString::null );
	uint version = 0;
	if( QRegExp( "[0-9]+\\.[0-9]" ).exactMatch( versionString ) )
		version = versionString.replace( '.', QString::null ).toUInt();

	if( version < ContactListVersion )
	{
		// The version string is invalid, or we're using an older version.
		// Convert first and reparse the file afterwards
		kdDebug() << k_funcinfo << "Contact list version " << version
			<< " is older than current version " << ContactListVersion
			<< ". Converting first." << endl;

		contactListFile.close();

		convertContactList( filename, version, ContactListVersion );

		contactList = QDomDocument ( "kopete-contact-list" );

		contactListFile.open( IO_ReadOnly );
		contactList.setContent( &contactListFile );

		list = contactList.documentElement();
	}

	QDomNode node = list.firstChild();
	while( !node.isNull() )
	{
		QDomElement element = node.toElement();
		if( !element.isNull() )
		{
			if( element.tagName() == "meta-contact" )
			{
				//TODO: id isn't used
				//QString id = element.attribute( "id", QString::null );
				KopeteMetaContact *metaContact = new KopeteMetaContact();

				QDomNode contactNode = node.firstChild();
				if ( !metaContact->fromXML( contactNode ) )
				{
					delete metaContact;
					metaContact = 0;
				}
				else
				{
					KopeteContactList::contactList()->addMetaContact(
						metaContact );
				}
			}
			else if( element.tagName() == "kopete-group" )
			{
				KopeteGroup *group = new KopeteGroup();
				if( !group->fromXML( node.firstChild() ) )
				{
					delete group;
					group = 0;
				}
				else
				{
					KopeteContactList::contactList()->addGroup( group );
				}
			}
			else
			{
				kdWarning(14010) << "KopeteContactList::loadXML: "
					  << "Unknown element '" << element.tagName()
					  << "' in contact list!" << endl;
			}

		}
		node = node.nextSibling();
	}
	contactListFile.close();
}

void KopeteContactList::convertContactList( const QString &fileName, uint /* fromVersion */, uint /* toVersion */ )
{
	// For now, ignore fromVersion and toVersion. These are meant for future
	// changes to allow incremental (multi-pass) conversion so we don't have
	// to rewrite the whole conversion code for each change.

	QDomDocument contactList( "messaging-contact-list" );
	QFile contactListFile( fileName );
	contactListFile.open( IO_ReadOnly );
	contactList.setContent( &contactListFile );

	QDomElement oldList = contactList.documentElement();

	QDomDocument newList( "kopete-contact-list" );
	newList.appendChild( newList.createProcessingInstruction( "xml", "version=\"1.0\"" ) );

	QDomElement newRoot = newList.createElement( "kopete-contact-list" );
	newList.appendChild( newRoot );
	newRoot.setAttribute( "version", "1.0" );

	QDomNode oldNode = oldList.firstChild();
	while( !oldNode.isNull() )
	{
		QDomElement oldElement = oldNode.toElement();
		if( !oldElement.isNull() )
		{
			if( oldElement.tagName() == "meta-contact" )
			{
				// Ignore ID, it is not used in the current list anyway
				QDomElement newMetaContact = newList.createElement( "meta-contact" );
				newRoot.appendChild( newMetaContact );

				// Plugin data is stored completely different, and requires
				// some bookkeeping to convert properly
				QMap<QString, QDomElement> pluginData;
				QStringList icqData;

				QDomNode oldContactNode = oldNode.firstChild();
				while( !oldContactNode.isNull() )
				{
					QDomElement oldContactElement = oldContactNode.toElement();
					if( !oldContactElement.isNull() )
					{
						if( oldContactElement.tagName() == "display-name" )
						{
							QDomElement displayName = newList.createElement( "display-name" );
							displayName.appendChild( newList.createTextNode( oldContactElement.text() ) );
							newMetaContact.appendChild( displayName );
						}
						else if( oldContactElement.tagName() == "groups" )
						{
							QDomElement groups = newList.createElement( "groups" );
							newMetaContact.appendChild( groups );

							QDomNode oldGroup = oldContactElement.firstChild();
							while( !oldGroup.isNull() )
							{
								QDomElement oldGroupElement = oldGroup.toElement();
								if ( oldGroupElement.tagName() == "group" )
								{
									QDomElement group = newList.createElement( "group" );
									group.appendChild( newList.createTextNode( oldGroupElement.text() ) );
									groups.appendChild( group );
								}
								else if ( oldGroupElement.tagName() == "top-level" )
								{
									QDomElement group = newList.createElement( "top-level" );
									groups.appendChild( group );
								}

								oldGroup = oldGroup.nextSibling();
							}
						}

						else if( oldContactElement.tagName() == "address-book-field" )
						{
							// Convert address book fields.
							// Jabber will be called "xmpp", Aim/Toc and Aim/Oscar both will
							// be called "aim". MSN, AIM, IRC, Oscar and SMS don't use address
							// book fields yet; Gadu and ICQ can be converted as-is.
							// As Yahoo is unfinished we won't try to convert at all.
							QString id   = oldContactElement.attribute( "id", QString::null );
							QString data = oldContactElement.text();

							QString app, key, val;
							QChar separator = ',';
							if( id == "messaging/gadu" )
								separator = '\n';
							else if( id == "messaging/icq" )
								separator = ';';
							else if( id == "messaging/jabber" )
								id = "messaging/xmpp";

							if( id == "messaging/gadu" || id == "messaging/icq" ||
								id == "messaging/winpopup" || id == "messaging/xmpp" )
							{
								app = id;
								key = "All";
								val = data.replace( separator, QChar( 0xE000 ) );
							}

							if( !app.isEmpty() )
							{
								QDomElement addressBookField = newList.createElement( "address-book-field" );
								newMetaContact.appendChild( addressBookField );

								addressBookField.setAttribute( "app", app );
								addressBookField.setAttribute( "key", key );

								addressBookField.appendChild( newList.createTextNode( val ) );

								// ICQ didn't store the contactId locally, only in the address
								// book fields, so we need to be able to access it later
								if( id == "messaging/icq" )
									icqData = QStringList::split( QChar( 0xE000 ), val );
							}
						}
						else if( oldContactElement.tagName() == "plugin-data" )
						{
							// Convert the plugin data
							QString id   = oldContactElement.attribute( "plugin-id", QString::null );
							QString data = oldContactElement.text();

							bool convertOldAim = false;
							uint fieldCount = 1;
							QString addressBookLabel;
							if( id == "MSNProtocol" )
							{
								fieldCount = 3;
								addressBookLabel = "msn";
							}
							else if( id == "IRCProtocol" )
							{
								fieldCount = 3;
								addressBookLabel = "irc";
							}
							else if( id == "OscarProtocol" )
							{
								fieldCount = 2;
								addressBookLabel = "aim";
							}
							else if( id == "AIMProtocol" )
							{
								id = "OscarProtocol";
								convertOldAim = true;
								addressBookLabel = "aim";
							}
							else if( id == "ICQProtocol" )
							{
								fieldCount = 1;
							}
							else if( id == "JabberProtocol" )
							{
								fieldCount = 4;
							}

							if( pluginData[ id ].isNull() )
							{
								pluginData[ id ] = newList.createElement( "plugin-data" );
								pluginData[ id ].setAttribute( "plugin-id", id );
								newMetaContact.appendChild( pluginData[ id ] );
							}

							QStringList strList = QStringList::split( "||", data );
							// Unescape '||'
							for( QStringList::iterator it = strList.begin(); it != strList.end(); ++it )
								( *it ).replace( QRegExp( "\\\\\\|;" ), "|" ).replace( QRegExp( "\\\\\\\\" ), "\\" );

							// Do the actual conversion
							if( id == "MSNProtocol" || id == "OscarProtocol" || id == "AIMProtocol" || id == "IRCProtocol" ||
								id == "ICQProtocol" || id == "JabberProtocol" )
							{
								uint idx = 0;
								while( idx < strList.size() )
								{
									QDomElement dataField;

									dataField = newList.createElement( "plugin-data-field" );
									pluginData[ id ].appendChild( dataField );
									dataField.setAttribute( "key", "contactId" );
									if( id == "ICQProtocol" )
										dataField.appendChild( newList.createTextNode( icqData[ idx ] ) );
									else if( id == "JabberProtocol" )
										dataField.appendChild( newList.createTextNode( strList[ idx + 1 ] ) );
									else
										dataField.appendChild( newList.createTextNode( strList[ idx ] ) );

									dataField = newList.createElement( "plugin-data-field" );
									pluginData[ id ].appendChild( dataField );
									dataField.setAttribute( "key", "displayName" );
									if( convertOldAim || id == "ICQProtocol" )
										dataField.appendChild( newList.createTextNode( strList[ idx ] ) );
									else if( id == "JabberProtocol" )
										dataField.appendChild( newList.createTextNode( strList[ idx + 2 ] ) );
									else
										dataField.appendChild( newList.createTextNode( strList[ idx + 1 ] ) );

									if( id == "MSNProtocol" )
									{
										dataField = newList.createElement( "plugin-data-field" );
										pluginData[ id ].appendChild( dataField );
										dataField.setAttribute( "key", "groups" );
										dataField.appendChild( newList.createTextNode( strList[ idx + 2 ] ) );
									}
									else if( id == "IRCProtocol" )
									{
										dataField = newList.createElement( "plugin-data-field" );
										pluginData[ id ].appendChild( dataField );
										dataField.setAttribute( "key", "serverName" );
										dataField.appendChild( newList.createTextNode( strList[ idx + 2 ] ) );
									}
									else if( id == "JabberProtocol" )
									{
										dataField = newList.createElement( "plugin-data-field" );
										pluginData[ id ].appendChild( dataField );
										dataField.setAttribute( "key", "identityId" );
										dataField.appendChild( newList.createTextNode( strList[ idx ] ) );

										dataField = newList.createElement( "plugin-data-field" );
										pluginData[ id ].appendChild( dataField );
										dataField.setAttribute( "key", "groups" );
										dataField.appendChild( newList.createTextNode( strList[ idx + 3 ] ) );
									}

									// MSN, AIM, IRC, Oscar and SMS didn't store address book fields up
									// to now, so create one
									if( id != "ICQProtocol" && id != "JabberProtocol" )
									{
										QDomElement addressBookField = newList.createElement( "address-book-field" );
										newMetaContact.appendChild( addressBookField );

										addressBookField.setAttribute( "app", "messaging/" + addressBookLabel );
										addressBookField.setAttribute( "key", "All" );
										addressBookField.appendChild( newList.createTextNode( strList[ idx ] ) );
									}

									idx += fieldCount;
								}
							}

							// TODO: Convert the actual data in 'data'
						}
					}
					oldContactNode = oldContactNode.nextSibling();
				}
			}
			else if( oldElement.tagName() == "kopete-group" )
			{
				QDomElement newGroup = newList.createElement( "kopete-group" );
				newRoot.appendChild( newGroup );

				QDomNode oldGroupNode = oldNode.firstChild();
				while( !oldGroupNode.isNull() )
				{
					QDomElement oldGroupElement = oldGroupNode.toElement();

					if( oldGroupElement.tagName() == "display-name" )
					{
						QDomElement displayName = newList.createElement( "display-name" );<commit_msg>Convert the non-protocol plugins. Only crypto is tested, but they should all work.<commit_after>/*
    kopetecontactlist.cpp - Kopete's Contact List backend

    Copyright (c) 2002 by <NAME>       <<EMAIL>>
    Copyright (c) 2002 by <NAME> <<EMAIL>>

    Copyright (c) 2002 by the Kopete developers  <<EMAIL>>

    *************************************************************************
    *                                                                       *
    * This program is free software; you can redistribute it and/or modify  *
    * it under the terms of the GNU General Public License as published by  *
    * the Free Software Foundation; either version 2 of the License, or     *
    * (at your option) any later version.                                   *
    *                                                                       *
    *************************************************************************
*/

#include "kopetecontactlist.h"

#include <qdom.h>
#include <qfile.h>
#include <qregexp.h>
#include <qstylesheet.h>

#include <kapplication.h>
#include <kdebug.h>
#include <kglobal.h>
#include <klocale.h>
#include <ksavefile.h>
#include <kstandarddirs.h>
#include <kmessagebox.h>
#include "kopetemetacontact.h"
#include "kopeteprotocol.h"
#include "pluginloader.h"
#include "kopetegroup.h"


KopeteContactList *KopeteContactList::s_contactList = 0L;

KopeteContactList *KopeteContactList::contactList()
{
	if( !s_contactList )
		s_contactList = new KopeteContactList;

	return s_contactList;
}

KopeteContactList::KopeteContactList()
: QObject( kapp, "KopeteContactList" )
{
}

KopeteContactList::~KopeteContactList()
{
// save is currently called in ~kopete (before the deletion of plugins)
//	save();
}

KopeteMetaContact *KopeteContactList::findContact( const QString &protocolId,
	const QString &identityId, const QString &contactId )
{
	QPtrListIterator<KopeteMetaContact> it( m_contacts );
	for( ; it.current(); ++it )
	{
		if( it.current()->findContact( protocolId, identityId, contactId ) )
			return it.current();
	}
	//kdDebug(14010) << k_funcinfo << "*** Not found!" << endl;
	return 0L;
}

void KopeteContactList::addMetaContact( KopeteMetaContact *mc )
{
	m_contacts.append( mc );

/*	connect( mc,
		SIGNAL( removedFromGroup( KopeteMetaContact *, const QString & ) ),
		SLOT( slotRemovedFromGroup( KopeteMetaContact *, const QString & ) ) );*/

	emit metaContactAdded( mc );
}

/*void KopeteContactList::slotRemovedFromGroup( KopeteMetaContact *mc,
	const QString &  )
{
	if( mc->groups().isEmpty() )
	{
		kdDebug(14010) << "KopeteContactList::slotRemovedFromGroup: "
			<< "contact removed from all groups: now toplevel." << endl;
		//m_contacts.remove( mc );
		//mc->deleteLater();
	}
}                  */

void KopeteContactList::loadXML()
{
	addGroup( KopeteGroup::toplevel );

	QString filename = locateLocal( "appdata", "contactlist.xml" );
	if( filename.isEmpty() )
		return ;

	QDomDocument contactList( "kopete-contact-list" );

	QFile contactListFile( filename );
	contactListFile.open( IO_ReadOnly );
	contactList.setContent( &contactListFile );

	QDomElement list = contactList.documentElement();

	QString versionString = list.attribute( "version", QString::null );
	uint version = 0;
	if( QRegExp( "[0-9]+\\.[0-9]" ).exactMatch( versionString ) )
		version = versionString.replace( '.', QString::null ).toUInt();

	if( version < ContactListVersion )
	{
		// The version string is invalid, or we're using an older version.
		// Convert first and reparse the file afterwards
		kdDebug() << k_funcinfo << "Contact list version " << version
			<< " is older than current version " << ContactListVersion
			<< ". Converting first." << endl;

		contactListFile.close();

		convertContactList( filename, version, ContactListVersion );

		contactList = QDomDocument ( "kopete-contact-list" );

		contactListFile.open( IO_ReadOnly );
		contactList.setContent( &contactListFile );

		list = contactList.documentElement();
	}

	QDomNode node = list.firstChild();
	while( !node.isNull() )
	{
		QDomElement element = node.toElement();
		if( !element.isNull() )
		{
			if( element.tagName() == "meta-contact" )
			{
				//TODO: id isn't used
				//QString id = element.attribute( "id", QString::null );
				KopeteMetaContact *metaContact = new KopeteMetaContact();

				QDomNode contactNode = node.firstChild();
				if ( !metaContact->fromXML( contactNode ) )
				{
					delete metaContact;
					metaContact = 0;
				}
				else
				{
					KopeteContactList::contactList()->addMetaContact(
						metaContact );
				}
			}
			else if( element.tagName() == "kopete-group" )
			{
				KopeteGroup *group = new KopeteGroup();
				if( !group->fromXML( node.firstChild() ) )
				{
					delete group;
					group = 0;
				}
				else
				{
					KopeteContactList::contactList()->addGroup( group );
				}
			}
			else
			{
				kdWarning(14010) << "KopeteContactList::loadXML: "
					  << "Unknown element '" << element.tagName()
					  << "' in contact list!" << endl;
			}

		}
		node = node.nextSibling();
	}
	contactListFile.close();
}

void KopeteContactList::convertContactList( const QString &fileName, uint /* fromVersion */, uint /* toVersion */ )
{
	// For now, ignore fromVersion and toVersion. These are meant for future
	// changes to allow incremental (multi-pass) conversion so we don't have
	// to rewrite the whole conversion code for each change.

	QDomDocument contactList( "messaging-contact-list" );
	QFile contactListFile( fileName );
	contactListFile.open( IO_ReadOnly );
	contactList.setContent( &contactListFile );

	QDomElement oldList = contactList.documentElement();

	QDomDocument newList( "kopete-contact-list" );
	newList.appendChild( newList.createProcessingInstruction( "xml", "version=\"1.0\"" ) );

	QDomElement newRoot = newList.createElement( "kopete-contact-list" );
	newList.appendChild( newRoot );
	newRoot.setAttribute( "version", "1.0" );

	QDomNode oldNode = oldList.firstChild();
	while( !oldNode.isNull() )
	{
		QDomElement oldElement = oldNode.toElement();
		if( !oldElement.isNull() )
		{
			if( oldElement.tagName() == "meta-contact" )
			{
				// Ignore ID, it is not used in the current list anyway
				QDomElement newMetaContact = newList.createElement( "meta-contact" );
				newRoot.appendChild( newMetaContact );

				// Plugin data is stored completely different, and requires
				// some bookkeeping to convert properly
				QMap<QString, QDomElement> pluginData;
				QStringList icqData;

				QDomNode oldContactNode = oldNode.firstChild();
				while( !oldContactNode.isNull() )
				{
					QDomElement oldContactElement = oldContactNode.toElement();
					if( !oldContactElement.isNull() )
					{
						if( oldContactElement.tagName() == "display-name" )
						{
							QDomElement displayName = newList.createElement( "display-name" );
							displayName.appendChild( newList.createTextNode( oldContactElement.text() ) );
							newMetaContact.appendChild( displayName );
						}
						else if( oldContactElement.tagName() == "groups" )
						{
							QDomElement groups = newList.createElement( "groups" );
							newMetaContact.appendChild( groups );

							QDomNode oldGroup = oldContactElement.firstChild();
							while( !oldGroup.isNull() )
							{
								QDomElement oldGroupElement = oldGroup.toElement();
								if ( oldGroupElement.tagName() == "group" )
								{
									QDomElement group = newList.createElement( "group" );
									group.appendChild( newList.createTextNode( oldGroupElement.text() ) );
									groups.appendChild( group );
								}
								else if ( oldGroupElement.tagName() == "top-level" )
								{
									QDomElement group = newList.createElement( "top-level" );
									groups.appendChild( group );
								}

								oldGroup = oldGroup.nextSibling();
							}
						}

						else if( oldContactElement.tagName() == "address-book-field" )
						{
							// Convert address book fields.
							// Jabber will be called "xmpp", Aim/Toc and Aim/Oscar both will
							// be called "aim". MSN, AIM, IRC, Oscar and SMS don't use address
							// book fields yet; Gadu and ICQ can be converted as-is.
							// As Yahoo is unfinished we won't try to convert at all.
							QString id   = oldContactElement.attribute( "id", QString::null );
							QString data = oldContactElement.text();

							QString app, key, val;
							QChar separator = ',';
							if( id == "messaging/gadu" )
								separator = '\n';
							else if( id == "messaging/icq" )
								separator = ';';
							else if( id == "messaging/jabber" )
								id = "messaging/xmpp";

							if( id == "messaging/gadu" || id == "messaging/icq" ||
								id == "messaging/winpopup" || id == "messaging/xmpp" )
							{
								app = id;
								key = "All";
								val = data.replace( separator, QChar( 0xE000 ) );
							}

							if( !app.isEmpty() )
							{
								QDomElement addressBookField = newList.createElement( "address-book-field" );
								newMetaContact.appendChild( addressBookField );

								addressBookField.setAttribute( "app", app );
								addressBookField.setAttribute( "key", key );

								addressBookField.appendChild( newList.createTextNode( val ) );

								// ICQ didn't store the contactId locally, only in the address
								// book fields, so we need to be able to access it later
								if( id == "messaging/icq" )
									icqData = QStringList::split( QChar( 0xE000 ), val );
							}
						}
						else if( oldContactElement.tagName() == "plugin-data" )
						{
							// Convert the plugin data
							QString id   = oldContactElement.attribute( "plugin-id", QString::null );
							QString data = oldContactElement.text();

							bool convertOldAim = false;
							uint fieldCount = 1;
							QString addressBookLabel;
							if( id == "MSNProtocol" )
							{
								fieldCount = 3;
								addressBookLabel = "msn";
							}
							else if( id == "IRCProtocol" )
							{
								fieldCount = 3;
								addressBookLabel = "irc";
							}
							else if( id == "OscarProtocol" )
							{
								fieldCount = 2;
								addressBookLabel = "aim";
							}
							else if( id == "AIMProtocol" )
							{
								id = "OscarProtocol";
								convertOldAim = true;
								addressBookLabel = "aim";
							}
							else if( id == "ICQProtocol" )
							{
								fieldCount = 1;
							}
							else if( id == "JabberProtocol" )
							{
								fieldCount = 4;
							}

							if( pluginData[ id ].isNull() )
							{
								pluginData[ id ] = newList.createElement( "plugin-data" );
								pluginData[ id ].setAttribute( "plugin-id", id );
								newMetaContact.appendChild( pluginData[ id ] );
							}

							// Do the actual conversion
							if( id == "MSNProtocol" || id == "OscarProtocol" || id == "AIMProtocol" || id == "IRCProtocol" ||
								id == "ICQProtocol" || id == "JabberProtocol" )
							{
								QStringList strList = QStringList::split( "||", data );
								// Unescape '||'
								for( QStringList::iterator it = strList.begin(); it != strList.end(); ++it )
									( *it ).replace( QRegExp( "\\\\\\|;" ), "|" ).replace( QRegExp( "\\\\\\\\" ), "\\" );

								uint idx = 0;
								while( idx < strList.size() )
								{
									QDomElement dataField;

									dataField = newList.createElement( "plugin-data-field" );
									pluginData[ id ].appendChild( dataField );
									dataField.setAttribute( "key", "contactId" );
									if( id == "ICQProtocol" )
										dataField.appendChild( newList.createTextNode( icqData[ idx ] ) );
									else if( id == "JabberProtocol" )
										dataField.appendChild( newList.createTextNode( strList[ idx + 1 ] ) );
									else
										dataField.appendChild( newList.createTextNode( strList[ idx ] ) );

									dataField = newList.createElement( "plugin-data-field" );
									pluginData[ id ].appendChild( dataField );
									dataField.setAttribute( "key", "displayName" );
									if( convertOldAim || id == "ICQProtocol" )
										dataField.appendChild( newList.createTextNode( strList[ idx ] ) );
									else if( id == "JabberProtocol" )
										dataField.appendChild( newList.createTextNode( strList[ idx + 2 ] ) );
									else
										dataField.appendChild( newList.createTextNode( strList[ idx + 1 ] ) );

									if( id == "MSNProtocol" )
									{
										dataField = newList.createElement( "plugin-data-field" );
										pluginData[ id ].appendChild( dataField );
										dataField.setAttribute( "key", "groups" );
										dataField.appendChild( newList.createTextNode( strList[ idx + 2 ] ) );
									}
									else if( id == "IRCProtocol" )
									{
										dataField = newList.createElement( "plugin-data-field" );
										pluginData[ id ].appendChild( dataField );
										dataField.setAttribute( "key", "serverName" );
										dataField.appendChild( newList.createTextNode( strList[ idx + 2 ] ) );
									}
									else if( id == "JabberProtocol" )
									{
										dataField = newList.createElement( "plugin-data-field" );
										pluginData[ id ].appendChild( dataField );
										dataField.setAttribute( "key", "identityId" );
										dataField.appendChild( newList.createTextNode( strList[ idx ] ) );

										dataField = newList.createElement( "plugin-data-field" );
										pluginData[ id ].appendChild( dataField );
										dataField.setAttribute( "key", "groups" );
										dataField.appendChild( newList.createTextNode( strList[ idx + 3 ] ) );
									}

									// MSN, AIM, IRC, Oscar and SMS didn't store address book fields up
									// to now, so create one
									if( id != "ICQProtocol" && id != "JabberProtocol" )
									{
										QDomElement addressBookField = newList.createElement( "address-book-field" );
										newMetaContact.appendChild( addressBookField );

										addressBookField.setAttribute( "app", "messaging/" + addressBookLabel );
										addressBookField.setAttribute( "key", "All" );
										addressBookField.appendChild( newList.createTextNode( strList[ idx ] ) );
									}

									idx += fieldCount;
								}
							}
							else if( id == "ContactNotesPlugin" || id == "CryptographyPlugin" || id == "TranslatorPlugin" )
							{
								QDomElement dataField = newList.createElement( "plugin-data-field" );
								pluginData[ id ].appendChild( dataField );
								if( id == "ContactNotesPlugin" )
									dataField.setAttribute( "key", "notes" );
								else if( id == "CryptographyPlugin" )
									dataField.setAttribute( "key", "gpgKey" );
								else if( id == "TranslatorPlugin" )
									dataField.setAttribute( "key", "languageKey" );

								dataField.appendChild( newList.createTextNode( data ) );
							}
						}
					}
					oldContactNode = oldContactNode.nextSibling();
				}
			}
			else if( oldElement.tagName() == "kopete-group" )
			{
				QDomElement newGroup = newList.createElement( "kopete-group" );
				newRoot.appendChild( newGroup );

				QDomNode oldGroupNode = oldNode.firstChild();
				while( !oldGroupNode.isNull() )
				{
					QDomElement oldGroupElement = oldGroupNode.toElement();

					if( oldGroupElement.tagName() == "display-name" )
					{
						QDomElement displayName = newList.createElement( "display-name" );
{'fraction_non_alphanumeric': 0.13907795473595977, 'fraction_numerical': 0.0041575859178541496, 'mean_word_length': 2.647548000489177, 'pattern_counts': {'":': 0, '<': 121, '<?xml version=': 0, '>': 59, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 4}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 14, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '904116', 'n_tokens_mistral': 12205, 'n_tokens_neox': 8939, 'n_words': 2094}