![]() |
XML Mill
1.0.0
A GUI based XML editor with a memory.
|
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 /*--------------------------------------------------------------------------------------*/