XML Mill  1.0.0
A GUI based XML editor with a memory.
gcdatabaseinterface.cpp
00001 /* Copyright (c) 2012 - 2013 by William Hallatt.
00002  *
00003  * This file forms part of "XML Mill".
00004  *
00005  * The official website for this project is <http://www.goblincoding.com> and,
00006  * although not compulsory, it would be appreciated if all works of whatever
00007  * nature using this source code (in whole or in part) include a reference to
00008  * this site.
00009  *
00010  * Should you wish to contact me for whatever reason, please do so via:
00011  *
00012  *                 <http://www.goblincoding.com/contact>
00013  *
00014  * This program is free software: you can redistribute it and/or modify it under
00015  * the terms of the GNU General Public License as published by the Free Software
00016  * Foundation, either version 3 of the License, or (at your option) any later
00017  * version.
00018  *
00019  * This program is distributed in the hope that it will be useful, but WITHOUT
00020  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
00021  * FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
00022  *
00023  * You should have received a copy of the GNU General Public License along with
00024  * this program (GNUGPL.txt).  If not, see
00025  *
00026  *                    <http://www.gnu.org/licenses/>
00027  */
00028 
00029 #include "gcdatabaseinterface.h"
00030 #include "gcbatchprocessorhelper.h"
00031 
00032 #include <QDomDocument>
00033 #include <QStringList>
00034 #include <QFile>
00035 #include <QTextStream>
00036 #include <QApplication>
00037 #include <QtSql/QSqlDatabase>
00038 #include <QtSql/QSqlError>
00039 #include <QtSql/QSqlRecord>
00040 #include <QtSql/QSqlField>
00041 
00042 /*--------------------------------------------------------------------------------------*/
00043 
00044 /* Have a look at "createTables" to see how the DB is set up. */
00045 static const QLatin1String INSERT_ELEMENT(
00046   "INSERT INTO xmlelements( element, children, attributes ) VALUES( ?, ?, ? )" );
00047 
00048 static const QLatin1String INSERT_ATTRIBUTEVALUES(
00049   "INSERT INTO xmlattributes( attribute, associatedElement, attributeValues ) VALUES( ?, ?, ? )" );
00050 
00051 static const QLatin1String UPDATE_CHILDREN(
00052   "UPDATE xmlelements SET children = ? WHERE element = ?" );
00053 
00054 static const QLatin1String UPDATE_ATTRIBUTES(
00055   "UPDATE xmlelements SET attributes = ? WHERE element = ?" );
00056 
00057 static const QLatin1String UPDATE_ATTRIBUTEVALUES(
00058   "UPDATE xmlattributes SET attributeValues = ? WHERE attribute = ? AND associatedElement = ?" );
00059 
00060 /*--------------------------------------------------------------------------------------*/
00061 
00062 /* Flat file containing list of databases. */
00063 static const QString DB_FILE( "dblist.txt" );
00064 
00065 /* Regular expression string to split "\" (Windows) or "/" (Unix) from file path. */
00066 static const QString REGEXP_SLASHES( "(\\\\|\\/)" );
00067 
00068 /* The database tables have fields containing strings of strings. For example, the
00069   "xmlelements" table maps a unique element against a list of all associated attribute
00070   values.  Since these values have to be entered into a single record, the easiest way
00071   is to insert a single (possibly massive) string containing all the associated attributes.
00072   To ensure that we can later extract the individual attributes again, we separate them with
00073   a sequence that should (theoretically) never be encountered.  This is that sequence. */
00074 static const QString SEPARATOR( "~!@" );
00075 
00076 /*--------------------------- NON-MEMBER UTILITY FUNCTIONS ----------------------------*/
00077 
00078 void cleanList( QStringList& list )
00079 {
00080   list.removeDuplicates();
00081   list.removeAll( "" );
00082 }
00083 
00084 /*--------------------------------------------------------------------------------------*/
00085 
00086 QString cleanAndJoinListElements( QStringList list )
00087 {
00088   cleanList( list );
00089   return list.join( SEPARATOR );
00090 }
00091 
00092 /*--------------------------------- MEMBER FUNCTIONS ----------------------------------*/
00093 
00094 GCDataBaseInterface* GCDataBaseInterface::m_instance = NULL;
00095 
00096 GCDataBaseInterface* GCDataBaseInterface::instance()
00097 {
00098   if( !m_instance )
00099   {
00100     m_instance = new GCDataBaseInterface;
00101   }
00102 
00103   return m_instance;
00104 }
00105 
00106 /*--------------------------------------------------------------------------------------*/
00107 
00108 GCDataBaseInterface::GCDataBaseInterface()
00109 : m_sessionDB       (),
00110   m_lastErrorMsg    ( "" ),
00111   m_hasActiveSession( false ),
00112   m_initialised     ( false ),
00113   m_dbMap           ()
00114 {
00115   QFile flatFile( DB_FILE );
00116 
00117   /* ReadWrite mode is required to create the file if it doesn't exist. */
00118   if( flatFile.open( QIODevice::ReadWrite | QIODevice::Text ) )
00119   {
00120     m_lastErrorMsg = "";
00121     m_initialised = true;
00122 
00123     QTextStream inStream( &flatFile );
00124     QString fileContent = inStream.readAll();
00125     flatFile.close();
00126 
00127     /* Split the input into separate lines (path/to/file lines). */
00128     QStringList list = fileContent.split( "\n", QString::SkipEmptyParts );
00129 
00130     foreach( QString str, list )
00131     {
00132       if( !addDatabase( str ) )
00133       {
00134         m_lastErrorMsg = QString( "Failed to load existing connection: \n %1" ).arg( str );
00135         m_initialised = false;
00136       }
00137     }
00138   }
00139   else
00140   {
00141     m_lastErrorMsg = QString( "Failed to access list of databases, file open error: [%1]." ).arg( flatFile.errorString() );
00142     m_initialised = false;
00143   }
00144 }
00145 
00146 /*--------------------------------------------------------------------------------------*/
00147 
00148 bool GCDataBaseInterface::isInitialised() const
00149 {
00150   return m_initialised;
00151 }
00152 
00153 /*--------------------------------------------------------------------------------------*/
00154 
00155 bool GCDataBaseInterface::batchProcessDomDocument( const QDomDocument* domDoc ) const
00156 {
00157   GCBatchProcessorHelper helper( domDoc,
00158                                  SEPARATOR,
00159                                  knownElements(),
00160                                  knownAttributeKeys() );
00161 
00162   qApp->processEvents( QEventLoop::ExcludeUserInputEvents );
00163 
00164   if( !addRootElement( domDoc->documentElement().tagName() ) )
00165   {
00166     /* Last error message is set in "addRootElement". */
00167     return false;
00168   }
00169 
00170   QSqlQuery query( m_sessionDB );
00171 
00172   /* Batch insert all the new elements. */
00173   if( !query.prepare( INSERT_ELEMENT ) )
00174   {
00175     m_lastErrorMsg = QString( "Prepare batch INSERT elements failed: [%1]" )
00176       .arg( query.lastError().text() );
00177     return false;
00178   }
00179 
00180   query.addBindValue( helper.newElementsToAdd() );
00181   query.addBindValue( helper.newElementChildrenToAdd() );
00182   query.addBindValue( helper.newElementAttributesToAdd() );
00183 
00184   if( !query.execBatch() )
00185   {
00186     m_lastErrorMsg = QString( "Batch INSERT elements failed: [%1]" )
00187       .arg( query.lastError().text() );
00188     return false;
00189   }
00190 
00191   qApp->processEvents( QEventLoop::ExcludeUserInputEvents );
00192 
00193   /* Batch update all the existing elements by concatenating the new values to the
00194     existing values. The second '?' represents our string SEPARATOR. */
00195   if( !query.prepare( "UPDATE xmlelements "
00196                       "SET children = ( IFNULL( ?, \"\" ) || IFNULL( ?, \"\" ) || IFNULL( children, \"\" ) ) "
00197                       "WHERE element = ?" ) )
00198   {
00199     m_lastErrorMsg = QString( "Prepare batch UPDATE element children failed: [%1]" )
00200       .arg( query.lastError().text() );
00201     return false;
00202   }
00203 
00204   /* Since we're doing batch updates we need to ensure that all the variant lists we provide
00205     have exactly the same size.  We furthermore require that the concatenation of new and old values
00206     are done in a way that includes our SEPARATOR string (which is why the separator list below). */
00207   QVariantList separatorList;
00208 
00209   for( int i = 0; i < helper.elementsToUpdate().size(); ++i )
00210   {
00211     separatorList << SEPARATOR;
00212   }
00213 
00214   query.addBindValue( helper.elementChildrenToUpdate() );
00215   query.addBindValue( separatorList );
00216   query.addBindValue( helper.elementsToUpdate() );
00217 
00218   if( !query.execBatch() )
00219   {
00220     m_lastErrorMsg = QString( "Batch UPDATE element children failed: [%1]" )
00221       .arg( query.lastError().text() );
00222     return false;
00223   }
00224 
00225   qApp->processEvents( QEventLoop::ExcludeUserInputEvents );
00226 
00227   if( !query.prepare( "UPDATE xmlelements "
00228                       "SET attributes = ( IFNULL( ?, \"\" ) || IFNULL( ?, \"\" ) || IFNULL( attributes, \"\" )  ) "
00229                       "WHERE element = ?" ) )
00230   {
00231     m_lastErrorMsg = QString( "Prepare batch UPDATE element attributes failed: [%1]" )
00232       .arg( query.lastError().text() );
00233     return false;
00234   }
00235 
00236   query.addBindValue( helper.elementAttributesToUpdate() );
00237   query.addBindValue( separatorList );
00238   query.addBindValue( helper.elementsToUpdate() );
00239 
00240   if( !query.execBatch() )
00241   {
00242     m_lastErrorMsg = QString( "Batch UPDATE element attributes failed: [%1]" )
00243       .arg( query.lastError().text() );
00244     return false;
00245   }
00246 
00247   qApp->processEvents( QEventLoop::ExcludeUserInputEvents );
00248 
00249   /* Batch insert all the new attribute values. */
00250   if( !query.prepare( INSERT_ATTRIBUTEVALUES ) )
00251   {
00252     m_lastErrorMsg = QString( "Prepare batch INSERT attribute values failed: [%1]" )
00253       .arg( query.lastError().text() );
00254     return false;
00255   }
00256 
00257   query.addBindValue( helper.newAttributeKeysToAdd() );
00258   query.addBindValue( helper.newAssociatedElementsToAdd() );
00259   query.addBindValue( helper.newAttributeValuesToAdd() );
00260 
00261   if( !query.execBatch() )
00262   {
00263     m_lastErrorMsg = QString( "Batch INSERT attribute values failed: [%1]" )
00264       .arg( query.lastError().text() );
00265     return false;
00266   }
00267 
00268   qApp->processEvents( QEventLoop::ExcludeUserInputEvents );
00269 
00270   /* Batch update all the existing attribute values. */
00271   if( !query.prepare( "UPDATE xmlattributes "
00272                       "SET attributeValues = ( IFNULL( ?, \"\" ) || IFNULL( ?, \"\" ) || IFNULL( attributeValues, \"\" ) ) "
00273                       "WHERE attribute = ? "
00274                       "AND associatedElement = ?" ) )
00275   {
00276     m_lastErrorMsg = QString( "Prepare batch UPDATE attribute values failed: [%1]" )
00277       .arg( query.lastError().text() );
00278     return false;
00279   }
00280 
00281   separatorList.clear();
00282 
00283   for( int i = 0; i < helper.attributeKeysToUpdate().size(); ++i )
00284   {
00285     separatorList << SEPARATOR;
00286   }
00287 
00288   query.addBindValue( helper.attributeValuesToUpdate() );
00289   query.addBindValue( separatorList );
00290   query.addBindValue( helper.attributeKeysToUpdate() );
00291   query.addBindValue( helper.associatedElementsToUpdate() );
00292 
00293   if( !query.execBatch() )
00294   {
00295     m_lastErrorMsg = QString( "Batch UPDATE attribute values failed: [%1]" )
00296       .arg( query.lastError().text() );
00297     return false;
00298   }
00299 
00300   return removeDuplicatesFromFields();
00301 }
00302 
00303 /*--------------------------------------------------------------------------------------*/
00304 
00305 bool GCDataBaseInterface::addElement( const QString& element, const QStringList& children, const QStringList& attributes ) const
00306 {
00307   if( element.isEmpty() )
00308   {
00309     m_lastErrorMsg = QString( "Trying to add an empty element name." );
00310     return false;
00311   }
00312 
00313   QSqlQuery query = selectElement( element );
00314 
00315   /* If we don't have an existing record, add it. */
00316   if( !query.first() )
00317   {
00318     if( !query.prepare( INSERT_ELEMENT ) )
00319     {
00320       m_lastErrorMsg = QString( "Prepare INSERT element failed for element \"%1\": [%2]" )
00321         .arg( element )
00322         .arg( query.lastError().text() );
00323       return false;
00324     }
00325 
00326     query.addBindValue( element );
00327     query.addBindValue( cleanAndJoinListElements( children ) );
00328     query.addBindValue( cleanAndJoinListElements( attributes ) );
00329 
00330     if( !query.exec() )
00331     {
00332       m_lastErrorMsg = QString( "INSERT element failed for element \"%1\": [%2]" )
00333         .arg( element )
00334         .arg( query.lastError().text() );
00335       return false;
00336     }
00337   }
00338 
00339   m_lastErrorMsg = "";
00340   return true;
00341 }
00342 
00343 /*--------------------------------------------------------------------------------------*/
00344 
00345 bool GCDataBaseInterface::addRootElement( const QString& root ) const
00346 {
00347   if( root.isEmpty() )
00348   {
00349     m_lastErrorMsg = QString( "Trying to add an empty root element name." );
00350     return false;
00351   }
00352 
00353   QSqlQuery query( m_sessionDB );
00354 
00355   if( !query.prepare( "SELECT * FROM rootelements WHERE root = ? " ) )
00356   {
00357     m_lastErrorMsg = QString( "Prepare SELECT root element failed for root \"%1\": [%2]" )
00358       .arg( root )
00359       .arg( query.lastError().text() );
00360     return false;
00361   }
00362 
00363   query.addBindValue( root );
00364 
00365   if( !query.exec() )
00366   {
00367     m_lastErrorMsg = QString( "SELECT root element failed for root \"%1\": [%2]" )
00368       .arg( root )
00369       .arg( query.lastError().text() );
00370     return false;
00371   }
00372 
00373   /* Make sure we aren't trying to insert a known root element. */
00374   if( !query.first() )
00375   {
00376     if( !query.prepare( "INSERT INTO rootelements ( root ) VALUES( ? )" ) )
00377     {
00378       m_lastErrorMsg = QString( "Prepare INSERT root element failed for root \"%1\": [%2]" )
00379         .arg( root )
00380         .arg( query.lastError().text() );
00381       return false;
00382     }
00383 
00384     query.addBindValue( root );
00385 
00386     if( !query.exec() )
00387     {
00388       m_lastErrorMsg = QString( "INSERT root element failed for element \"%1\": [%2]" )
00389         .arg( root )
00390         .arg( query.lastError().text() );
00391       return false;
00392     }
00393   }
00394 
00395   m_lastErrorMsg = "";
00396   return true;
00397 }
00398 
00399 /*--------------------------------------------------------------------------------------*/
00400 
00401 bool GCDataBaseInterface::updateElementChildren( const QString& element, const QStringList& children, bool replace ) const
00402 {
00403   if( element.isEmpty() )
00404   {
00405     m_lastErrorMsg = QString( "Invalid element name provided." );
00406     return false;
00407   }
00408 
00409   QSqlQuery query = selectElement( element );
00410 
00411   /* Update the existing record (if we have one). */
00412   if( query.first() )
00413   {
00414     QStringList allChildren( children );
00415 
00416     if( !replace )
00417     {
00418       allChildren.append( query.record().field( "children" ).value().toString().split( SEPARATOR ) );
00419     }
00420 
00421     if( !query.prepare( UPDATE_CHILDREN ) )
00422     {
00423       m_lastErrorMsg = QString( "Prepare UPDATE element children failed for element \"%1\": [%2]" )
00424         .arg( element )
00425         .arg( query.lastError().text() );
00426       return false;
00427     }
00428 
00429     query.addBindValue( cleanAndJoinListElements( allChildren ) );
00430     query.addBindValue( element );
00431 
00432     if( !query.exec() )
00433     {
00434       m_lastErrorMsg = QString( "UPDATE children failed for element \"%1\": [%2]" )
00435         .arg( element )
00436         .arg( query.lastError().text() );
00437       return false;
00438     }
00439   }
00440   else
00441   {
00442     m_lastErrorMsg = QString( "No knowledge of element \"%1\", add it first." )
00443       .arg( element );
00444     return false;
00445   }
00446 
00447   m_lastErrorMsg = "";
00448   return true;
00449 }
00450 
00451 /*--------------------------------------------------------------------------------------*/
00452 
00453 bool GCDataBaseInterface::updateElementAttributes( const QString& element, const QStringList& attributes, bool replace ) const
00454 {
00455   if( element.isEmpty() )
00456   {
00457     m_lastErrorMsg = QString( "Invalid element name provided." );
00458     return false;
00459   }
00460 
00461   QSqlQuery query = selectElement( element );
00462 
00463   /* Update the existing record (if we have one). */
00464   if( query.first() )
00465   {
00466     QStringList allAttributes;
00467 
00468     if( !replace )
00469     {
00470       allAttributes.append( query.record().field( "attributes" ).value().toString().split( SEPARATOR ) );
00471     }
00472 
00473     /* Add it here so that we append the new attributes to the end of the list. */
00474     allAttributes.append( attributes );
00475 
00476     if( !query.prepare( UPDATE_ATTRIBUTES ) )
00477     {
00478       m_lastErrorMsg = QString( "Prepare UPDATE element attribute failed for element \"%1\": [%2]" )
00479         .arg( element )
00480         .arg( query.lastError().text() );
00481       return false;
00482     }
00483 
00484     query.addBindValue( cleanAndJoinListElements( allAttributes ) );
00485     query.addBindValue( element );
00486 
00487     if( !query.exec() )
00488     {
00489       m_lastErrorMsg = QString( "UPDATE attribute failed for element \"%1\": [%2]" )
00490         .arg( element )
00491         .arg( query.lastError().text() );
00492       return false;
00493     }
00494   }
00495   else
00496   {
00497     m_lastErrorMsg = QString( "No element \"%1\" exists." )
00498       .arg( element );
00499     return false;
00500   }
00501 
00502   m_lastErrorMsg = "";
00503   return true;
00504 }
00505 
00506 /*--------------------------------------------------------------------------------------*/
00507 
00508 bool GCDataBaseInterface::updateAttributeValues( const QString& element, const QString& attribute, const QStringList& attributeValues, bool replace ) const
00509 {
00510   if( element.isEmpty() || attribute.isEmpty() )
00511   {
00512     m_lastErrorMsg = QString( "Invalid element or attribute values provided." );
00513     return false;
00514   }
00515 
00516   QSqlQuery query = selectAttribute( attribute, element );
00517 
00518   /* If we don't have an existing record, add it, otherwise update the existing one. */
00519   if( !query.first() )
00520   {
00521     if( !query.prepare( INSERT_ATTRIBUTEVALUES ) )
00522     {
00523       m_lastErrorMsg = QString( "Prepare INSERT attribute value failed for element \"%1\": [%2]" )
00524         .arg( element )
00525         .arg( query.lastError().text() );
00526       return false;
00527     }
00528 
00529     query.addBindValue( attribute );
00530     query.addBindValue( element );
00531     query.addBindValue( cleanAndJoinListElements( attributeValues ) );
00532 
00533     if( !query.exec() )
00534     {
00535       m_lastErrorMsg = QString( "INSERT attribute failed for element \"%1\": [%2]" )
00536         .arg( element )
00537         .arg( query.lastError().text() );
00538       return false;
00539     }
00540   }
00541   else
00542   {
00543     QStringList existingValues( attributeValues );
00544 
00545     if( !replace )
00546     {
00547       existingValues.append( query.record().field( "attributeValues" ).value().toString().split( SEPARATOR ) );
00548     }
00549 
00550     /* The reason for not using concatenating values here is that we don't simply want to add
00551       all the supposed new values, we want to make sure they are all unique by removing all duplicates
00552       before sticking it all back into the DB. */
00553     if( !query.prepare( UPDATE_ATTRIBUTEVALUES ) )
00554     {
00555       m_lastErrorMsg = QString( "Prepare UPDATE attribute values failed for element \"%1\" and attribute \"%2\": [%3]" )
00556         .arg( element )
00557         .arg( attribute )
00558         .arg( query.lastError().text() );
00559       return false;
00560     }
00561 
00562     query.addBindValue( cleanAndJoinListElements( existingValues ) );
00563     query.addBindValue( attribute );
00564     query.addBindValue( element );
00565 
00566     if( !query.exec() )
00567     {
00568       m_lastErrorMsg = QString( "UPDATE attribute values failed for element \"%1\" and attribute [%2]: [%3]" )
00569         .arg( element )
00570         .arg( attribute )
00571         .arg( query.lastError().text() );
00572       return false;
00573     }
00574   }
00575 
00576   m_lastErrorMsg = "";
00577   return true;
00578 }
00579 
00580 /*--------------------------------------------------------------------------------------*/
00581 
00582 bool GCDataBaseInterface::removeElement( const QString& element ) const
00583 {
00584   QSqlQuery query = selectElement( element );
00585 
00586   /* Only continue if we have an existing record. */
00587   if( query.first() )
00588   {
00589     if( !query.prepare( "DELETE FROM xmlelements WHERE element = ?" ) )
00590     {
00591       m_lastErrorMsg = QString( "Prepare DELETE element failed for element \"%1\": [%3]" )
00592         .arg( element )
00593         .arg( query.lastError().text() );
00594       return false;
00595     }
00596 
00597     query.addBindValue( element );
00598 
00599     if( !query.exec() )
00600     {
00601       m_lastErrorMsg = QString( "DELETE element failed for element \"%1\": [%3]" )
00602         .arg( element )
00603         .arg( query.lastError().text() );
00604       return false;
00605     }
00606   }
00607 
00608   m_lastErrorMsg = "";
00609   return true;
00610 }
00611 
00612 /*--------------------------------------------------------------------------------------*/
00613 
00614 bool GCDataBaseInterface::removeChildElement( const QString& element, const QString& child ) const
00615 {
00616   QSqlQuery query = selectElement( element );
00617 
00618   /* Update the existing record (if we have one). */
00619   if( query.first() )
00620   {
00621     QStringList allChildren( query.record().field( "children" ).value().toString().split( SEPARATOR ) );
00622     allChildren.removeAll( child );
00623     updateElementChildren( element, allChildren, true );
00624   }
00625 
00626   m_lastErrorMsg = "";
00627   return true;
00628 }
00629 
00630 /*--------------------------------------------------------------------------------------*/
00631 
00632 bool GCDataBaseInterface::removeAttribute( const QString& element, const QString& attribute ) const
00633 {
00634   QSqlQuery query = selectAttribute( attribute, element );
00635 
00636   /* Only continue if we have an existing record. */
00637   if( query.first() )
00638   {
00639     if( !query.prepare( "DELETE FROM xmlattributes "
00640                         "WHERE attribute = ? "
00641                         "AND associatedElement = ?" ) )
00642     {
00643       m_lastErrorMsg = QString( "Prepare DELETE attribute failed for element \"%1\" and attribute \"%2\": [%3]" )
00644         .arg( element )
00645         .arg( attribute )
00646         .arg( query.lastError().text() );
00647       return false;
00648     }
00649 
00650     query.addBindValue( attribute );
00651     query.addBindValue( element );
00652 
00653     if( !query.exec() )
00654     {
00655       m_lastErrorMsg = QString( "DELETE attribute failed for element \"%1\" and attribute [%2]: [%3]" )
00656         .arg( element )
00657         .arg( attribute )
00658         .arg( query.lastError().text() );
00659       return false;
00660     }
00661   }
00662 
00663   query = selectElement( element );
00664   QStringList allAttributes = attributes( element );
00665   allAttributes.removeAll( attribute );
00666   updateElementAttributes( element, allAttributes, true );
00667 
00668   m_lastErrorMsg = "";
00669   return true;
00670 }
00671 
00672 /*--------------------------------------------------------------------------------------*/
00673 
00674 bool GCDataBaseInterface::removeRootElement( const QString& element ) const
00675 {
00676   QSqlQuery query( m_sessionDB );
00677 
00678   if( !query.prepare( "DELETE FROM rootelements WHERE root = ?" ) )
00679   {
00680     m_lastErrorMsg = QString( "Prepare DELETE failed for root \"%1\": [%2]" )
00681       .arg( element )
00682       .arg( query.lastError().text() );
00683     return false;
00684   }
00685 
00686   query.addBindValue( element );
00687 
00688   if( !query.exec() )
00689   {
00690     m_lastErrorMsg = QString( "DELETE root element failed for root \"%1\": [%2]" )
00691       .arg( element )
00692       .arg( query.lastError().text() );
00693     return false;
00694   }
00695 
00696   m_lastErrorMsg = "";
00697   return true;
00698 }
00699 
00700 /*--------------------------------------------------------------------------------------*/
00701 
00702 bool GCDataBaseInterface::hasActiveSession() const
00703 {
00704   return m_hasActiveSession;
00705 }
00706 
00707 /*--------------------------------------------------------------------------------------*/
00708 
00709 QString GCDataBaseInterface::activeSessionName() const
00710 {
00711   if( m_hasActiveSession )
00712   {
00713     return m_sessionDB.connectionName();
00714   }
00715 
00716   return QString();
00717 }
00718 
00719 /*--------------------------------------------------------------------------------------*/
00720 
00721 bool GCDataBaseInterface::isProfileEmpty() const
00722 {
00723   return knownRootElements().isEmpty();
00724 }
00725 
00726 /*--------------------------------------------------------------------------------------*/
00727 
00728 bool GCDataBaseInterface::isUniqueChildElement( const QString& parentElement, const QString& element ) const
00729 {
00730   QSqlQuery query = selectAllElements();
00731 
00732   while( query.next() )
00733   {
00734     if( query.record().field( "element" ).value().toString() != parentElement &&
00735         query.record().value( "children" ).toString().split( SEPARATOR ).contains( element ) )
00736     {
00737       return false;
00738     }
00739   }
00740 
00741   return true;
00742 }
00743 
00744 /*--------------------------------------------------------------------------------------*/
00745 
00746 bool GCDataBaseInterface::isDocumentCompatible( const QDomDocument* doc ) const
00747 {
00748   GCBatchProcessorHelper helper( doc,
00749                                  SEPARATOR,
00750                                  knownElements(),
00751                                  knownAttributeKeys() );
00752 
00753   qApp->processEvents( QEventLoop::ExcludeUserInputEvents );
00754 
00755   /* If there are any new elements or attributes to add, the document is incompatible (not checking
00756     for new attribute values since new values don't affect XML relationships, i.e. it isn't important
00757     enough to import entire documents each time an unknown value is encountered) */
00758   if( !helper.newAssociatedElementsToAdd().isEmpty() ||
00759       !helper.newAttributeKeysToAdd().isEmpty() ||
00760       !helper.newElementAttributesToAdd().isEmpty() ||
00761       !helper.newElementChildrenToAdd().isEmpty() ||
00762       !helper.newElementsToAdd().isEmpty() )
00763   {
00764     return false;
00765   }
00766 
00767   /* If there are no new items, we may still have previously unknown relationships which we
00768     need to check.  This is slightly more involved than simply checking for empty lists.
00769     Also remember that the lists returned by GCBatchProcessorHelper are all synchronised
00770     with regards to their indices (which is the only reason why we can loop through the
00771     lists like this). */
00772   for( int i = 0; i < helper.elementsToUpdate().size(); ++i )
00773   {
00774     /* If any new element children were added, we have an incompatible document. */
00775     QStringList knownChildren = children( helper.elementsToUpdate().at( i ).toString() );
00776     QStringList allChildren = QStringList() << knownChildren << helper.elementChildrenToUpdate().at( i ).toString().split( SEPARATOR );
00777     cleanList( allChildren );
00778 
00779     /* Check for larger since we only care about added items. */
00780     if( allChildren.size() > knownChildren.size() )
00781     {
00782       return false;
00783     }
00784 
00785     /* If any new attributes were added, we also have an incompatible document. */
00786     QStringList knownAttributes = attributes( helper.elementsToUpdate().at( i ).toString() );
00787     QStringList allAttributes = QStringList() << knownAttributes << helper.elementAttributesToUpdate().at( i ).toString().split( SEPARATOR );
00788     cleanList( allAttributes );
00789 
00790     if( allAttributes.size() > knownAttributes.size() )
00791     {
00792       return false;
00793     }
00794   }
00795 
00796   return true;
00797 }
00798 
00799 /*--------------------------------------------------------------------------------------*/
00800 
00801 QStringList GCDataBaseInterface::knownElements() const
00802 {
00803   QSqlQuery query = selectAllElements();
00804 
00805   m_lastErrorMsg = "";
00806 
00807   QStringList elementNames;
00808 
00809   while( query.next() )
00810   {
00811     elementNames.append( query.record().field( "element" ).value().toString() );
00812   }
00813 
00814   cleanList( elementNames );
00815   elementNames.sort();
00816   return elementNames;
00817 }
00818 
00819 /*--------------------------------------------------------------------------------------*/
00820 
00821 QStringList GCDataBaseInterface::children( const QString& element ) const
00822 {
00823   QSqlQuery query = selectElement( element );
00824 
00825   /* There should be only one record corresponding to this element. */
00826   if( !query.first() )
00827   {
00828     m_lastErrorMsg = QString( "Failed to obtain the list of children for element \"%1\"" )
00829       .arg( element );
00830     return QStringList();
00831   }
00832 
00833   m_lastErrorMsg = "";
00834 
00835   QStringList children = query.record().value( "children" ).toString().split( SEPARATOR );
00836   cleanList( children );
00837   children.sort();
00838   return children;
00839 }
00840 
00841 /*--------------------------------------------------------------------------------------*/
00842 
00843 QStringList GCDataBaseInterface::attributes( const QString& element ) const
00844 {
00845   QSqlQuery query = selectElement( element );
00846 
00847   /* There should be only one record corresponding to this element. */
00848   if( !query.first() )
00849   {
00850     m_lastErrorMsg = QString( "Failed to obtain the list of attributes for element \"%1\"" )
00851       .arg( element );
00852     return QStringList();
00853   }
00854 
00855   m_lastErrorMsg = "";
00856 
00857   QStringList attributes = query.record().value( "attributes" ).toString().split( SEPARATOR );
00858   cleanList( attributes );
00859   return attributes;
00860 }
00861 
00862 /*--------------------------------------------------------------------------------------*/
00863 
00864 QStringList GCDataBaseInterface::attributeValues( const QString& element, const QString& attribute ) const
00865 {
00866   QSqlQuery query = selectAttribute( attribute, element );
00867 
00868   /* There should be only one record corresponding to this element. */
00869   if( !query.first() )
00870   {
00871     m_lastErrorMsg = QString( "Failed to obtain the list of attribute values for attribute \"%1\"" )
00872       .arg( attribute );
00873     return QStringList();
00874   }
00875 
00876   m_lastErrorMsg = "";
00877 
00878   QStringList attributeValues = query.record().value( "attributeValues" ).toString().split( SEPARATOR );
00879   cleanList( attributeValues );
00880   attributeValues.sort();
00881   return attributeValues;
00882 }
00883 
00884 /*--------------------------------------------------------------------------------------*/
00885 
00886 QStringList GCDataBaseInterface::knownRootElements() const
00887 {
00888   return knownRootElements( m_sessionDB );
00889 }
00890 
00891 /*--------------------------------------------------------------------------------------*/
00892 
00893 QStringList GCDataBaseInterface::knownRootElements( QSqlDatabase db ) const
00894 {
00895   QSqlQuery query( db );
00896 
00897   if( !query.exec( "SELECT * FROM rootelements" ) )
00898   {
00899     m_lastErrorMsg = QString( "SELECT all root elements failed: [%1]" )
00900       .arg( query.lastError().text() );
00901     return QStringList();
00902   }
00903 
00904   m_lastErrorMsg = "";
00905 
00906   QStringList rootElements;
00907 
00908   while( query.next() )
00909   {
00910     rootElements.append( query.record().field( "root" ).value().toString() );
00911   }
00912 
00913   cleanList( rootElements );
00914   return rootElements;
00915 }
00916 
00917 /*--------------------------------------------------------------------------------------*/
00918 
00919 bool GCDataBaseInterface::containsKnownRootElement( const QString& dbName, const QString& root ) const
00920 {
00921   /* In case the db name passed in consists of a path/to/file string. */
00922   QString dbConName = dbName.split( QRegExp( REGEXP_SLASHES ), QString::SkipEmptyParts ).last();
00923 
00924   /* No error messages are logged for this specific query since we aren't necessarily concerned with
00925     the session we're querying (it may not be the active session). */
00926   if( QSqlDatabase::contains( dbConName ) )
00927   {
00928     QSqlDatabase db = QSqlDatabase::database( dbConName );
00929 
00930     if( db.isValid() && db.open() )
00931     {
00932       if( knownRootElements( db ).contains( root ) )
00933       {
00934         return true;
00935       }
00936       else
00937       {
00938         return false;
00939       }
00940     }
00941   }
00942 
00943   return false;
00944 }
00945 
00946 /*--------------------------------------------------------------------------------------*/
00947 
00948 QStringList GCDataBaseInterface::connectionList() const
00949 {
00950   return m_dbMap.keys();
00951 }
00952 
00953 /*--------------------------------------------------------------------------------------*/
00954 
00955 const QString& GCDataBaseInterface::lastError() const
00956 {
00957   return m_lastErrorMsg;
00958 }
00959 
00960 /*--------------------------------------------------------------------------------------*/
00961 
00962 bool GCDataBaseInterface::addDatabase( const QString& dbName )
00963 {
00964   if( !dbName.isEmpty() )
00965   {
00966     /* In case the db name passed in consists of a path/to/file string. */
00967     QString dbConName = dbName.split( QRegExp( REGEXP_SLASHES ), QString::SkipEmptyParts ).last();
00968 
00969     if( !m_dbMap.contains( dbConName ) )
00970     {
00971       QSqlDatabase db = QSqlDatabase::addDatabase( "QSQLITE", dbConName );
00972 
00973       if( db.isValid() )
00974       {
00975         db.setDatabaseName( dbName );
00976         m_dbMap.insert( dbConName, dbName );
00977         saveDatabaseFile();
00978 
00979         m_lastErrorMsg = "";
00980         return true;
00981       }
00982 
00983       m_lastErrorMsg = QString( "Failed to add database \"%1\": [%2]." ).arg( dbConName ).arg( db.lastError().text() );
00984       return false;
00985     }
00986     else
00987     {
00988       m_lastErrorMsg = QString( "Connection \"%1\" already exists." ).arg( dbConName );
00989       return false;
00990     }
00991   }
00992 
00993   m_lastErrorMsg = QString( "Database name is empty." );
00994   return false;
00995 }
00996 
00997 /*--------------------------------------------------------------------------------------*/
00998 
00999 bool GCDataBaseInterface::removeDatabase( const QString& dbName )
01000 {
01001   if( !dbName.isEmpty() )
01002   {
01003     /* In case the db name passed in consists of a path/to/file string. */
01004     QString dbConName = dbName.split( QRegExp( REGEXP_SLASHES ), QString::SkipEmptyParts ).last();
01005 
01006     if( m_sessionDB.connectionName() == dbConName )
01007     {
01008       m_sessionDB.close();
01009       m_hasActiveSession = false;
01010     }
01011 
01012     QFile file( m_dbMap.value( dbConName ) );
01013 
01014     if( !file.remove() )
01015     {
01016       m_lastErrorMsg = QString( "Failed to remove database file: [%1]" )
01017         .arg( dbName );
01018       return false;
01019     }
01020 
01021     /* If the DB connection being removed was also the active one, "removeDatabase" will output
01022       a warning (to the Qt IDE's "Application Output" window).  This is purely because we have a
01023       DB member variable and isn't cause for concern as there seems to be no way around it with
01024       the current QtSQL modules. */
01025     QSqlDatabase::removeDatabase( dbConName );
01026     m_dbMap.remove( dbConName );
01027     saveDatabaseFile();
01028 
01029     m_lastErrorMsg = "";
01030     return true;
01031   }
01032 
01033   m_lastErrorMsg = QString( "Database name is empty." );
01034   return false;
01035 }
01036 
01037 /*--------------------------------------------------------------------------------------*/
01038 
01039 bool GCDataBaseInterface::setActiveDatabase( const QString& dbName )
01040 {
01041   /* In case the db name passed in consists of a path/to/file string. */
01042   QString dbConName = dbName.split( QRegExp( REGEXP_SLASHES ), QString::SkipEmptyParts ).last();
01043 
01044   if( QSqlDatabase::contains( dbConName ) )
01045   {
01046     if( openConnection( dbConName ) )
01047     {
01048       m_hasActiveSession = true;
01049       return true;
01050     }
01051   }
01052   else
01053   {
01054     /* If we set a DB for the session that doesn't exist (new, unknown),
01055       then we'll automatically try to add it and set it as active. */
01056     if( addDatabase( dbName ) )
01057     {
01058       if( openConnection( dbConName ) )
01059       {
01060         m_hasActiveSession = true;
01061         return true;
01062       }
01063     }
01064 
01065     m_hasActiveSession = false;
01066     return false;
01067   }
01068 
01069   m_hasActiveSession = false;
01070   return false;
01071 }
01072 
01073 /*--------------------------------------------------------------------------------------*/
01074 
01075 QStringList GCDataBaseInterface::knownAttributeKeys() const
01076 {
01077   QSqlQuery query = selectAllAttributes();
01078 
01079   m_lastErrorMsg = "";
01080 
01081   QStringList attributeNames;
01082 
01083   while( query.next() )
01084   {
01085     /* Concatenate the attribute name and associated element into a single string
01086       so that it is easier to determine whether a record already exists for that
01087       particular combination (this is used in GCBatchProcessorHelper). */
01088     attributeNames.append( query.record().field( "attribute" ).value().toString() +
01089                            "!" +
01090                            query.record().field( "associatedElement" ).value().toString() );
01091   }
01092 
01093   return attributeNames;
01094 }
01095 
01096 /*--------------------------------------------------------------------------------------*/
01097 
01098 QSqlQuery GCDataBaseInterface::selectElement( const QString& element ) const
01099 {
01100   /* See if we already have this element in the DB. */
01101   QSqlQuery query( m_sessionDB );
01102 
01103   if( !query.prepare( "SELECT * FROM xmlelements WHERE element = ?" ) )
01104   {
01105     m_lastErrorMsg = QString( "Prepare SELECT failed for element \"%1\": [%2]" )
01106       .arg( element )
01107       .arg( query.lastError().text() );
01108   }
01109 
01110   query.addBindValue( element );
01111 
01112   if( !query.exec() )
01113   {
01114     m_lastErrorMsg = QString( "SELECT element failed for element \"%1\": [%2]" )
01115       .arg( element )
01116       .arg( query.lastError().text() );
01117   }
01118 
01119   return query;
01120 }
01121 
01122 /*--------------------------------------------------------------------------------------*/
01123 
01124 QSqlQuery GCDataBaseInterface::selectAllElements() const
01125 {
01126   QSqlQuery query( m_sessionDB );
01127 
01128   if( !query.exec( "SELECT * FROM xmlelements" ) )
01129   {
01130     m_lastErrorMsg = QString( "SELECT all root elements failed: [%1]" )
01131       .arg( query.lastError().text() );
01132   }
01133 
01134   return query;
01135 }
01136 
01137 /*--------------------------------------------------------------------------------------*/
01138 
01139 QSqlQuery GCDataBaseInterface::selectAttribute( const QString& attribute, const QString& associatedElement ) const
01140 {
01141   QSqlQuery query( m_sessionDB );
01142 
01143   if( !query.prepare( "SELECT * FROM xmlattributes "
01144                       "WHERE attribute = ? "
01145                       "AND associatedElement = ?" ) )
01146   {
01147     m_lastErrorMsg = QString( "Prepare SELECT attribute failed for attribute \"%1\" and element \"%2\": [%3]" )
01148       .arg( attribute )
01149       .arg( associatedElement )
01150       .arg( query.lastError().text() );
01151   }
01152 
01153   query.addBindValue( attribute );
01154   query.addBindValue( associatedElement );
01155 
01156   if( !query.exec() )
01157   {
01158     m_lastErrorMsg = QString( "SELECT attribute failed for attribute \"%1\" and element \"%2\": [%3]" )
01159       .arg( attribute )
01160       .arg( associatedElement )
01161       .arg( query.lastError().text() );
01162   }
01163 
01164   return query;
01165 }
01166 
01167 /*--------------------------------------------------------------------------------------*/
01168 
01169 QSqlQuery GCDataBaseInterface::selectAllAttributes() const
01170 {
01171   QSqlQuery query( m_sessionDB );
01172 
01173   if( !query.exec( "SELECT * FROM xmlattributes" ) )
01174   {
01175     m_lastErrorMsg = QString( "SELECT all attribute values failed: [%1]" )
01176       .arg( query.lastError().text() );
01177   }
01178 
01179   return query;
01180 }
01181 
01182 /*--------------------------------------------------------------------------------------*/
01183 
01184 bool GCDataBaseInterface::removeDuplicatesFromFields() const
01185 {
01186   /* Remove duplicates and update the element records. */
01187   QStringList elementNames = knownElements();
01188   QString element( "" );
01189 
01190   for( int i = 0; i < elementNames.size(); ++i )
01191   {
01192     element = elementNames.at( i );
01193     QSqlQuery query = selectElement( element );
01194 
01195     /* Not checking for query validity since the table may still be empty when
01196       this funciton gets called (i.e. there is a potentially valid reason for cases
01197       where no valid records exist). */
01198     if( query.first() )
01199     {
01200       QStringList allChildren  ( query.record().field( "children" ).value().toString().split( SEPARATOR ) );
01201       QStringList allAttributes( query.record().field( "attributes" ).value().toString().split( SEPARATOR ) );
01202 
01203       if( !query.prepare( UPDATE_CHILDREN ) )
01204       {
01205         m_lastErrorMsg = QString( "Prepare UPDATE element children failed for element \"%1\": [%2]" )
01206           .arg( element )
01207           .arg( query.lastError().text() );
01208         return false;
01209       }
01210 
01211       query.addBindValue( cleanAndJoinListElements( allChildren ) );
01212       query.addBindValue( element );
01213 
01214       if( !query.exec() )
01215       {
01216         m_lastErrorMsg = QString( "UPDATE children failed for element \"%1\": [%2]" )
01217           .arg( element )
01218           .arg( query.lastError().text() );
01219         return false;
01220       }
01221 
01222       if( !query.prepare( UPDATE_ATTRIBUTES ) )
01223       {
01224         m_lastErrorMsg = QString( "Prepare UPDATE element attributes failed for element \"%1\": [%2]" )
01225           .arg( element )
01226           .arg( query.lastError().text() );
01227         return false;
01228       }
01229 
01230       query.addBindValue( cleanAndJoinListElements( allAttributes ) );
01231       query.addBindValue( element );
01232 
01233       if( !query.exec() )
01234       {
01235         m_lastErrorMsg = QString( "UPDATE attributes failed for element \"%1\": [%2]" )
01236           .arg( element )
01237           .arg( query.lastError().text() );
01238         return false;
01239       }
01240 
01241       qApp->processEvents( QEventLoop::ExcludeUserInputEvents );
01242     }
01243   }
01244 
01245   /* Remove duplicates and update the attribute values records. */
01246   QStringList attributeKeys = knownAttributeKeys();
01247 
01248   /* Not checking for query validity since the table may still be empty when
01249     this funciton gets called (i.e. there is a potentially valid reason for cases
01250     where no valid records exist). */
01251   for( int i = 0; i < attributeKeys.size(); ++i )
01252   {
01253     QString attribute = attributeKeys.at( i ).split( "!" ).at( 0 );
01254     QString associatedElement = attributeKeys.at( i ).split( "!" ).at( 1 );
01255     QSqlQuery query = selectAttribute( attribute, associatedElement );
01256 
01257     /* Does a record for this attribute exist? */
01258     if( query.first() )
01259     {
01260       QStringList allValues( query.record().field( "attributeValues" ).value().toString().split( SEPARATOR ) );
01261 
01262       if( !query.prepare( UPDATE_ATTRIBUTEVALUES ) )
01263       {
01264         m_lastErrorMsg = QString( "Prepare UPDATE attribute values failed for element \"%1\" and attribute \"%2\": [%3]" )
01265           .arg( associatedElement )
01266           .arg( attribute )
01267           .arg( query.lastError().text() );
01268         return false;
01269       }
01270 
01271       query.addBindValue( cleanAndJoinListElements( allValues ) );
01272       query.addBindValue( attribute );
01273       query.addBindValue( associatedElement );
01274 
01275       if( !query.exec() )
01276       {
01277         m_lastErrorMsg = QString( "UPDATE attribute values failed for element \"%1\" and attribute \"%2\": [%3]" )
01278           .arg( associatedElement )
01279           .arg( attribute )
01280           .arg( query.lastError().text() );
01281         return false;
01282       }
01283 
01284       qApp->processEvents( QEventLoop::ExcludeUserInputEvents );
01285     }
01286   }
01287 
01288   m_lastErrorMsg = "";
01289   return true;
01290 }
01291 
01292 /*--------------------------------------------------------------------------------------*/
01293 
01294 bool GCDataBaseInterface::openConnection( const QString& dbConName )
01295 {
01296   /* If we have a previous connection open, close it. */
01297   if( m_sessionDB.isValid() && m_sessionDB.isOpen() )
01298   {
01299     m_sessionDB.close();
01300   }
01301 
01302   /* Open the new connection. */
01303   m_sessionDB = QSqlDatabase::database( dbConName );
01304 
01305   if( m_sessionDB.isValid() )
01306   {
01307     if( !m_sessionDB.open() )
01308     {
01309       m_lastErrorMsg = QString( "Failed to open database \"%1\": [%2]." )
01310         .arg( m_dbMap.value( dbConName ) )
01311         .arg( m_sessionDB.lastError().text() );
01312       return false;
01313     }
01314 
01315     /* If the DB has not yet been initialised. */
01316     QStringList tables = m_sessionDB.tables();
01317 
01318     if( !tables.contains( "xmlelements", Qt::CaseInsensitive ) )
01319     {
01320       return createTables();
01321     }
01322   }
01323   else
01324   {
01325     m_lastErrorMsg = QString( "Failed to open a valid session connection \"%1\": [%2]" )
01326       .arg( dbConName )
01327       .arg( m_sessionDB.lastError().text() );
01328     return false;
01329   }
01330 
01331   m_lastErrorMsg = "";
01332   return true;
01333 }
01334 
01335 /*--------------------------------------------------------------------------------------*/
01336 
01337 bool GCDataBaseInterface::createTables() const
01338 {
01339   /* DB connection will be open from openConnection() above so no need to do any checks here. */
01340   QSqlQuery query( m_sessionDB );
01341 
01342   if( !query.exec( "CREATE TABLE xmlelements( element QString primary key, children QString, attributes QString )" ) )
01343   {
01344     m_lastErrorMsg = QString( "Failed to create elements table for \"%1\": [%2]." )
01345       .arg( m_sessionDB.connectionName() )
01346       .arg( query.lastError().text() );
01347     return false;
01348   }
01349 
01350   if( !query.exec( "CREATE TABLE xmlattributes( attribute QString, associatedElement QString, attributeValues QString, "
01351                    "UNIQUE(attribute, associatedElement), "
01352                    "FOREIGN KEY(associatedElement) REFERENCES xmlelements(element) )" ) )
01353   {
01354     m_lastErrorMsg = QString( "Failed to create attribute values table for \"%1\": [%2]" )
01355       .arg( m_sessionDB.connectionName() )
01356       .arg( query.lastError().text() );
01357     return false;
01358   }
01359   else
01360   {
01361     if( !query.exec( "CREATE UNIQUE INDEX attributeKey ON xmlattributes( attribute, associatedElement)" ) )
01362     {
01363       m_lastErrorMsg = QString( "Failed to create unique index for \"%1\": [%2]" )
01364         .arg( m_sessionDB.connectionName() )
01365         .arg( query.lastError().text() );
01366       return false;
01367     }
01368   }
01369 
01370   if( !query.exec( "CREATE TABLE rootelements( root QString primary key )" ) )
01371   {
01372     m_lastErrorMsg = QString( "Failed to create root elements table for \"%1\": [%2]" )
01373       .arg( m_sessionDB.connectionName() )
01374       .arg( query.lastError().text() );
01375     return false;
01376   }
01377 
01378   m_lastErrorMsg = "";
01379   return true;
01380 }
01381 
01382 /*--------------------------------------------------------------------------------------*/
01383 
01384 void GCDataBaseInterface::saveDatabaseFile() const
01385 {
01386   QFile flatFile( DB_FILE );
01387 
01388   if( flatFile.open( QIODevice::ReadWrite | QIODevice::Text | QIODevice::Truncate ) )
01389   {
01390     QTextStream outStream( &flatFile );
01391 
01392     foreach( QString str, m_dbMap.values() )
01393     {
01394       outStream << str << "\n";
01395     }
01396 
01397     flatFile.close();
01398   }
01399 }
01400 
01401 /*--------------------------------------------------------------------------------------*/