XML Mill  1.0.0
A GUI based XML editor with a memory.
gcbatchprocessorhelper.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 "gcbatchprocessorhelper.h"
00030 
00031 #include <QDomDocument>
00032 
00033 /*--------------------------------- MEMBER FUNCTIONS ----------------------------------*/
00034 
00035 GCBatchProcessorHelper::GCBatchProcessorHelper( const QDomDocument* domDoc,
00036                                                 const QString& stringListSeparator,
00037                                                 const QStringList& knownElements,
00038                                                 const QStringList& knownAttributes )
00039 : m_stringListSeparator       ( stringListSeparator ),
00040   m_knownElements             ( knownElements ),
00041   m_knownAttributeKeys        ( knownAttributes ),
00042   m_newElementsToAdd          (),
00043   m_newElementChildrenToAdd   (),
00044   m_newElementAttributesToAdd (),
00045   m_elementsToUpdate          (),
00046   m_elementChildrenToUpdate   (),
00047   m_elementAttributesToUpdate (),
00048   m_newAttributeKeysToAdd     (),
00049   m_newAssociatedElementsToAdd(),
00050   m_newAttributeValuesToAdd   (),
00051   m_attributeKeysToUpdate     (),
00052   m_associatedElementsToUpdate(),
00053   m_attributeValuesToUpdate   (),
00054   m_unsorted                  (),
00055   m_records                   ()
00056 {
00057   QDomElement root = domDoc->documentElement();
00058   createRecord( root );
00059   processElement( root );   // kicks off a chain of recursive DOM element traversals
00060   sortRecords();
00061   createVariantLists();
00062 }
00063 
00064 /*--------------------------------------------------------------------------------------*/
00065 
00066 void GCBatchProcessorHelper::processElement( const QDomElement& parentElement )
00067 {
00068   QDomElement element = parentElement.firstChildElement();
00069 
00070   while( !element.isNull() )
00071   {
00072     createRecord( element );
00073     processElement( element );
00074     element = element.nextSiblingElement();
00075   }
00076 }
00077 
00078 /*--------------------------------------------------------------------------------------*/
00079 
00080 void GCBatchProcessorHelper::createRecord( const QDomElement& element )
00081 {
00082   ElementRecord record;
00083 
00084   /* Stick the attributes and their corresponding values into the record map. */
00085   QDomNamedNodeMap attributeNodes = element.attributes();
00086 
00087   for( int i = 0; i < attributeNodes.size(); ++i )
00088   {
00089     QDomAttr attribute = attributeNodes.item( i ).toAttr();
00090 
00091     if( !attribute.isNull() )
00092     {
00093       record.attributes.insert( attribute.name(), QStringList( attribute.value() ) );
00094     }
00095   }
00096 
00097   /* Collect all the first level (element) children associated with this element. */
00098   QDomNodeList children = element.childNodes();
00099 
00100   for( int i = 0; i < children.size(); ++i )
00101   {
00102     QDomNode child = children.at( i );
00103 
00104     if( child.isElement() )
00105     {
00106       record.children.append( child.toElement().tagName() );
00107     }
00108   }
00109 
00110   /* We'll sort it all later, for now get the recursive calls out of the way. */
00111   m_unsorted.insert( element.tagName(), record );
00112 }
00113 
00114 /*--------------------------------------------------------------------------------------*/
00115 
00116 void GCBatchProcessorHelper::sortRecords()
00117 {
00118   /* We inserted every element in the DOM doc into the unsorted map and will almost
00119     definitely have duplicates.  Since the DB requires unique element entries (elements
00120     are the primary keys), we iterate through the unique keys and consolidate the entries
00121     per element. */
00122   foreach( QString element, m_unsorted.uniqueKeys() )
00123   {
00124    /* Retrieve all the records associated with this element. */
00125    QList< ElementRecord > duplicateRecords = m_unsorted.values( element );
00126 
00127    /* If we have more than one record for the same element, then we need to consolidate
00128      all the attributes associated with this particular element. */
00129    if( duplicateRecords.size() > 1 )
00130    {
00131      ElementRecord record;
00132 
00133      /* Just stick all the attributes and their values into a map for now
00134        so that we don't have to run the consolidation functionality on
00135        each iteration...that would just be silly... */
00136      QMultiMap< QString, QStringList > recordAttributes;
00137 
00138      for( int i = 0; i < duplicateRecords.size(); ++i )
00139      {
00140        recordAttributes += duplicateRecords.at( i ).attributes;
00141 
00142        /* Don't consolidate the attribute values here (see above comment) but since
00143          we're already iterating through the list, we may as well consolidate the
00144          children while we're at it (they are relatively simple to handle compared
00145          to the attribute value maps). */
00146        record.children.append( duplicateRecords.at( i ).children );
00147      }
00148 
00149      record.children.removeDuplicates();
00150 
00151      /* Now we can sort out the chaos. For each unique attribute, we will obtain
00152        the associated QStringList(s) of attribute values and consolidate the lot. */
00153      foreach( QString attribute, recordAttributes.uniqueKeys() )
00154      {
00155       QList< QStringList > attributeValues = recordAttributes.values( attribute );
00156       QStringList finalListOfAttributeValues;
00157 
00158       for( int j = 0; j < attributeValues.size(); ++j )
00159       {
00160         finalListOfAttributeValues.append( attributeValues.at( j ) );
00161       }
00162 
00163       finalListOfAttributeValues.removeDuplicates();
00164       record.attributes.insert( attribute, finalListOfAttributeValues );
00165      }
00166 
00167      m_records.insert( element, record );
00168    }
00169    else
00170    {
00171      /* If we only have one entry for this particular element name, we can
00172        safely insert it into the sorted map. */
00173      ElementRecord record = duplicateRecords.at( 0 );
00174      record.children.removeDuplicates();
00175      m_records.insert( element, record );
00176    }
00177   }
00178 }
00179 
00180 /*--------------------------------------------------------------------------------------*/
00181 
00182 void GCBatchProcessorHelper::createVariantLists()
00183 {
00184   /* First see which of the records we created from the DOM doc are completely new
00185     and which ones we have prior knowledge of. */
00186   QList< QString > elementNames = m_records.keys();
00187 
00188   for( int i = 0; i < elementNames.size(); ++i )
00189   {
00190     if( !m_knownElements.contains( elementNames.at( i ) ) )
00191     {
00192       m_newElementsToAdd << elementNames.at( i );
00193     }
00194     else
00195     {
00196       m_elementsToUpdate << elementNames.at( i );
00197     }
00198   }
00199 
00200   /* Deal with all the new elements first. */
00201   foreach( QVariant var, m_newElementsToAdd )
00202   {
00203    QString element = var.toString();
00204 
00205    /* Do we have first level children? */
00206    if( !m_records.value( element ).children.isEmpty() )
00207    {
00208      m_newElementChildrenToAdd << m_records.value( element ).children.join( m_stringListSeparator );
00209    }
00210    else
00211    {
00212      /* To keep the indices in sync, the following will result in a NULL value
00213        being bound to the relevant prepared query on the DB side (we need to have
00214        exactly the same number of items in all lists associated with the "new elements"
00215        in order for the batch bind to succeed). The same argument holds for the
00216        "elements to update" and two types of attribute lists below. */
00217      m_newElementChildrenToAdd << QVariant( QVariant::String );
00218    }
00219 
00220    /* Do we have associated attributes? */
00221    if( !m_records.value( element ).attributes.keys().isEmpty() )
00222    {
00223      QStringList attributeNames = m_records.value( element ).attributes.keys();
00224      m_newElementAttributesToAdd << attributeNames.join( m_stringListSeparator );
00225    }
00226    else
00227    {
00228      /* See comment for "m_newElementChildrenToAdd" above.*/
00229      m_newElementAttributesToAdd << QVariant( QVariant::String );
00230    }
00231   }
00232 
00233   /* Now we deal with all the elements that will have to be updated. */
00234   foreach( QVariant var, m_elementsToUpdate )
00235   {
00236    QString element = var.toString();
00237 
00238    /* Do we have first level children? */
00239    if( !m_records.value( element ).children.isEmpty() )
00240    {
00241      m_elementChildrenToUpdate << m_records.value( element ).children.join( m_stringListSeparator );
00242    }
00243    else
00244    {
00245      /* See comment for "m_newElementChildrenToAdd" above.*/
00246      m_elementChildrenToUpdate << QVariant( QVariant::String );
00247    }
00248 
00249    /* Do we have associated attributes? */
00250    if( !m_records.value( element ).attributes.keys().isEmpty() )
00251    {
00252      QStringList attributeNames = m_records.value( element ).attributes.keys();
00253      m_elementAttributesToUpdate << attributeNames.join( m_stringListSeparator );
00254    }
00255    else
00256    {
00257      /* See comment for "m_newElementChildrenToAdd" above.*/
00258      m_elementAttributesToUpdate << QVariant( QVariant::String );
00259    }
00260   }
00261 
00262   /* Separate the new attribute keys and associated values from the existing ones. */
00263   foreach( QString element, elementNames )
00264   {
00265     QList< QString > attributeNames = m_records.value( element ).attributes.keys();
00266 
00267     foreach( QString attribute, attributeNames )
00268     {
00269       QStringList attributeValues = m_records.value( element ).attributes.value( attribute );
00270       attributeValues.removeDuplicates();
00271 
00272       /* Do we know about this attribute? (by the way, the "!" is a separator
00273         used to create a unique string name from the element and associated
00274         attribute for ease of comparison with the attribute keys list we get
00275         given...this is not ideal, but the only solution I have at the moment. */
00276       if( !m_knownAttributeKeys.contains( attribute + "!" + element ) )
00277       {
00278         m_newAttributeKeysToAdd << attribute;
00279         m_newAssociatedElementsToAdd << element;
00280 
00281         if( !attributeValues.isEmpty() )
00282         {
00283           m_newAttributeValuesToAdd << attributeValues.join( m_stringListSeparator );
00284         }
00285         else
00286         {
00287           /* See comment for "m_newElementChildrenToAdd" above.*/
00288           m_newAttributeValuesToAdd << QVariant( QVariant::String );
00289         }
00290       }
00291       else
00292       {
00293         m_attributeKeysToUpdate << attribute;
00294         m_associatedElementsToUpdate << element;
00295 
00296         if( !attributeValues.isEmpty() )
00297         {
00298           m_attributeValuesToUpdate << attributeValues.join( m_stringListSeparator );
00299         }
00300         else
00301         {
00302           /* See comment for "m_newElementChildrenToAdd" above.*/
00303           m_attributeValuesToUpdate << QVariant( QVariant::String );
00304         }
00305       }
00306     }
00307   }
00308 }
00309 
00310 /*--------------------------------------------------------------------------------------*/
00311 
00312 const QVariantList& GCBatchProcessorHelper::newElementsToAdd() const
00313 {
00314   return m_newElementsToAdd;
00315 }
00316 
00317 /*--------------------------------------------------------------------------------------*/
00318 
00319 const QVariantList& GCBatchProcessorHelper::newElementChildrenToAdd() const
00320 {
00321   return m_newElementChildrenToAdd;
00322 }
00323 
00324 /*--------------------------------------------------------------------------------------*/
00325 
00326 const QVariantList& GCBatchProcessorHelper::newElementAttributesToAdd() const
00327 {
00328   return m_newElementAttributesToAdd;
00329 }
00330 
00331 /*--------------------------------------------------------------------------------------*/
00332 
00333 const QVariantList& GCBatchProcessorHelper::elementsToUpdate() const
00334 {
00335   return m_elementsToUpdate;
00336 }
00337 
00338 /*--------------------------------------------------------------------------------------*/
00339 
00340 const QVariantList& GCBatchProcessorHelper::elementChildrenToUpdate() const
00341 {
00342   return m_elementChildrenToUpdate;
00343 }
00344 
00345 /*--------------------------------------------------------------------------------------*/
00346 
00347 const QVariantList& GCBatchProcessorHelper::elementAttributesToUpdate() const
00348 {
00349   return m_elementAttributesToUpdate;
00350 }
00351 
00352 /*--------------------------------------------------------------------------------------*/
00353 
00354 const QVariantList& GCBatchProcessorHelper::newAttributeKeysToAdd() const
00355 {
00356   return m_newAttributeKeysToAdd;
00357 }
00358 
00359 /*--------------------------------------------------------------------------------------*/
00360 
00361 const QVariantList& GCBatchProcessorHelper::newAssociatedElementsToAdd() const
00362 {
00363   return m_newAssociatedElementsToAdd;
00364 }
00365 /*--------------------------------------------------------------------------------------*/
00366 
00367 const QVariantList& GCBatchProcessorHelper::newAttributeValuesToAdd() const
00368 {
00369   return m_newAttributeValuesToAdd;
00370 }
00371 
00372 /*--------------------------------------------------------------------------------------*/
00373 
00374 const QVariantList& GCBatchProcessorHelper::attributeKeysToUpdate() const
00375 {
00376   return m_attributeKeysToUpdate;
00377 }
00378 
00379 /*--------------------------------------------------------------------------------------*/
00380 
00381 const QVariantList& GCBatchProcessorHelper::associatedElementsToUpdate() const
00382 {
00383   return m_associatedElementsToUpdate;
00384 }
00385 
00386 /*--------------------------------------------------------------------------------------*/
00387 
00388 const QVariantList& GCBatchProcessorHelper::attributeValuesToUpdate() const
00389 {
00390   return m_attributeValuesToUpdate;
00391 }
00392 
00393 /*--------------------------------------------------------------------------------------*/