filters

csvexport.cc

00001 /* This file is part of the KDE project
00002    Copyright (C) 2000 David Faure <faure@kde.org>
00003    Copyright (C) 2004 Nicolas GOUTTE <goutte@kde.org>
00004 
00005    This library is free software; you can redistribute it and/or
00006    modify it under the terms of the GNU Library General Public
00007    License as published by the Free Software Foundation; either
00008    version 2 of the License, or (at your option) any later version.
00009 
00010    This library is distributed in the hope that it will be useful,
00011    but WITHOUT ANY WARRANTY; without even the implied warranty of
00012    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
00013    Library General Public License for more details.
00014 
00015    You should have received a copy of the GNU Library General Public License
00016    along with this library; see the file COPYING.LIB.  If not, write to
00017    the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
00018  * Boston, MA 02110-1301, USA.
00019 */
00020 
00021 #include <csvexport.h>
00022 
00023 #include <qfile.h>
00024 #include <qtextcodec.h>
00025 
00026 #include <kdebug.h>
00027 #include <kmessagebox.h>
00028 #include <kgenericfactory.h>
00029 #include <KoFilterChain.h>
00030 #include <KoFilterManager.h>
00031 
00032 #include <kspread_map.h>
00033 #include <kspread_sheet.h>
00034 #include <kspread_doc.h>
00035 #include <kspread_view.h>
00036 #include <selection.h>
00037 
00038 #include <csvexportdialog.h>
00039 
00040 using namespace KSpread;
00041 
00042 typedef KGenericFactory<CSVExport, KoFilter> CSVExportFactory;
00043 K_EXPORT_COMPONENT_FACTORY( libcsvexport, CSVExportFactory( "kofficefilters" ) )
00044 
00045 class Cell
00046 {
00047  public:
00048   int row, col;
00049   QString text;
00050 
00051   bool operator < ( const Cell & c ) const
00052   {
00053     return row < c.row || ( row == c.row && col < c.col );
00054   }
00055   bool operator == ( const Cell & c ) const
00056   {
00057     return row == c.row && col == c.col;
00058   }
00059 };
00060 
00061 
00062 CSVExport::CSVExport( KoFilter *, const char *, const QStringList & )
00063   : KoFilter(), m_eol("\n")
00064 {
00065 }
00066 
00067 QString CSVExport::exportCSVCell( Sheet const * const sheet, int col, int row, QChar const & textQuote, QChar csvDelimiter )
00068 {
00069   // This function, given a cell, returns a string corresponding to its export in CSV format
00070   // It proceeds by:
00071   //  - getting the value of the cell, if any
00072   //  - protecting quote characters within cells, if any
00073   //  - enclosing the cell in quotes if the cell is non empty
00074 
00075   KSpread::Cell const * const cell = sheet->cellAt( col, row );
00076   QString text;
00077 
00078   if ( !cell->isDefault() && !cell->isEmpty() )
00079   {
00080     if ( cell->isFormula() )
00081         text = cell->strOutText();
00082     else if ( !cell->link().isEmpty() )
00083         text = cell->text(); // untested
00084     else if( cell->isTime() )
00085         text = cell->value().asTime().toString("hh:mm:ss");
00086     else if( cell->isDate() )
00087         text = cell->value().asDate().toString("yyyy-MM-dd");
00088     else
00089         text = cell->strOutText();
00090   }
00091 
00092   // quote only when needed (try to mimic excel)
00093   bool quote = false;
00094   if ( !text.isEmpty() )
00095   {
00096     if ( text.find( textQuote ) != -1 )
00097     {
00098       QString doubleTextQuote(textQuote);
00099       doubleTextQuote.append(textQuote);
00100       text.replace(textQuote, doubleTextQuote);
00101       quote = true;
00102 
00103     } else if ( text[0].isSpace() || text[text.length()-1].isSpace() )
00104       quote = true;
00105     else if ( text.find( csvDelimiter ) != -1 )
00106       quote = true;
00107     else if ( text.find( "\n" ) != -1 || text.find( "\r" ) != -1 )
00108       quote = true;
00109   }
00110 
00111   if ( quote ) {
00112     text.prepend(textQuote);
00113     text.append(textQuote);
00114   }
00115 
00116   return text;
00117 }
00118 
00119 // The reason why we use the KoDocument* approach and not the QDomDocument
00120 // approach is because we don't want to export formulas but values !
00121 KoFilter::ConversionStatus CSVExport::convert( const QCString & from, const QCString & to )
00122 {
00123   kdDebug(30501) << "CSVExport::convert" << endl;
00124   KoDocument* document = m_chain->inputDocument();
00125 
00126   if ( !document )
00127     return KoFilter::StupidError;
00128 
00129   if ( !::qt_cast<const KSpread::Doc *>( document ) )
00130   {
00131     kdWarning(30501) << "document isn't a KSpread::Doc but a " << document->className() << endl;
00132     return KoFilter::NotImplemented;
00133   }
00134   if ( ( to != "text/x-csv" && to != "text/plain" ) || from != "application/x-kspread" )
00135   {
00136     kdWarning(30501) << "Invalid mimetypes " << to << " " << from << endl;
00137     return KoFilter::NotImplemented;
00138   }
00139 
00140   Doc const * const ksdoc = static_cast<const Doc *>(document);
00141 
00142   if ( ksdoc->mimeType() != "application/x-kspread" )
00143   {
00144     kdWarning(30501) << "Invalid document mimetype " << ksdoc->mimeType() << endl;
00145     return KoFilter::NotImplemented;
00146   }
00147 
00148   CSVExportDialog *expDialog = 0;
00149   if (!m_chain->manager()->getBatchMode())
00150   {
00151     expDialog= new CSVExportDialog( 0 );
00152 
00153     if (!expDialog)
00154     {
00155       kdError(30501) << "Dialog has not been created! Aborting!" << endl;
00156       return KoFilter::StupidError;
00157     }
00158     expDialog->fillSheet( ksdoc->map() );
00159 
00160     if ( !expDialog->exec() )
00161     {
00162       delete expDialog;
00163       return KoFilter::UserCancelled;
00164     }
00165   }
00166 
00167   QTextCodec* codec = 0;
00168   QChar csvDelimiter;
00169   if (expDialog)
00170   {
00171     codec = expDialog->getCodec();
00172     if ( !codec )
00173     {
00174       delete expDialog;
00175       return KoFilter::StupidError;
00176     }
00177     csvDelimiter = expDialog->getDelimiter();
00178     m_eol = expDialog->getEndOfLine();
00179   }
00180   else
00181   {
00182     codec = QTextCodec::codecForName("UTF-8");
00183     csvDelimiter = ',';
00184   }
00185 
00186 
00187   // Now get hold of the sheet to export
00188   // (Hey, this could be part of the dialog too, choosing which sheet to export....
00189   //  It's great to have parametrable filters... IIRC even MSOffice doesn't have that)
00190   // Ok, for now we'll use the first sheet - my document has only one sheet anyway ;-)))
00191 
00192   bool first = true;
00193   QString str;
00194   QChar textQuote;
00195   if (expDialog)
00196     textQuote = expDialog->getTextQuote();
00197   else
00198     textQuote = '"';
00199 
00200   if ( expDialog && expDialog->exportSelectionOnly() )
00201   {
00202     kdDebug(30501) << "Export as selection mode" << endl;
00203     View const * const view = static_cast<View*>(ksdoc->views().getFirst());
00204 
00205     if ( !view ) // no view if embedded document
00206     {
00207       delete expDialog;
00208       return KoFilter::StupidError;
00209     }
00210 
00211     Sheet const * const sheet = view->activeSheet();
00212 
00213     QRect selection = view->selectionInfo()->lastRange();
00214     // Compute the highest row and column indexes (within the selection)
00215     // containing non-empty cells, respectively called CSVMaxRow CSVMaxCol.
00216     // The CSV will have CSVMaxRow rows, all with CSVMaxCol columns
00217     int right       = selection.right();
00218     int bottom      = selection.bottom();
00219     int CSVMaxRow   = 0;
00220     int CSVMaxCol   = 0;
00221 
00222     for ( int idxRow = 1, row = selection.top(); row <= bottom; ++row, ++idxRow )
00223     {
00224       for ( int idxCol = 1, col = selection.left(); col <= right; ++col, ++idxCol )
00225       {
00226         if( ! sheet->cellAt( col, row )->isEmpty() )
00227         {
00228           if ( idxRow > CSVMaxRow )
00229             CSVMaxRow = idxRow;
00230 
00231           if ( idxCol > CSVMaxCol )
00232             CSVMaxCol = idxCol;
00233         }
00234       }
00235     }
00236 
00237     for ( int idxRow = 1, row = selection.top();
00238           row <= bottom && idxRow <= CSVMaxRow; ++row, ++idxRow )
00239     {
00240       int idxCol = 1;
00241       for ( int col = selection.left();
00242             col <= right && idxCol <= CSVMaxCol; ++col, ++idxCol )
00243       {
00244         str += exportCSVCell( sheet, col, row, textQuote, csvDelimiter );
00245 
00246         if ( idxCol < CSVMaxCol )
00247           str += csvDelimiter;
00248       }
00249 
00250       // This is to deal with the case of non-rectangular selections 
00251       for ( ; idxCol < CSVMaxCol; ++idxCol )
00252           str += csvDelimiter;
00253 
00254       str += m_eol;
00255     }
00256   }
00257   else
00258   {
00259     kdDebug(30501) << "Export as full mode" << endl;
00260     QPtrListIterator<Sheet> it( ksdoc->map()->sheetList() );
00261     for( ; it.current(); ++it )
00262     {
00263       Sheet const * const sheet = it.current();
00264 
00265       if (expDialog && !expDialog->exportSheet( sheet->sheetName() ) )
00266       {
00267         continue;
00268       }
00269 
00270       // Compute the highest row and column indexes containing non-empty cells,
00271       // respectively called CSVMaxRow CSVMaxCol.
00272       // The CSV will have CSVMaxRow rows, all with CSVMaxCol columns
00273       int sheetMaxRow = sheet->maxRow();
00274       int sheetMaxCol = sheet->maxColumn();
00275       int CSVMaxRow   = 0;
00276       int CSVMaxCol   = 0;
00277 
00278       for ( int row = 1 ; row <= sheetMaxRow ; ++row)
00279       {
00280         for ( int col = 1 ; col <= sheetMaxCol ; col++ )
00281         {
00282           if( ! sheet->cellAt( col, row )->isEmpty() )
00283           {
00284             if ( row > CSVMaxRow )
00285               CSVMaxRow = row;
00286 
00287             if ( col > CSVMaxCol )
00288               CSVMaxCol = col;
00289           }
00290         }
00291       }
00292 
00293       // Skip the sheet altogether if it is empty
00294       if ( CSVMaxRow + CSVMaxCol == 0)
00295         continue;
00296 
00297       kdDebug(30501) << "Max row x column: " << CSVMaxRow << " x " << CSVMaxCol << endl;
00298 
00299       // Print sheet separators, except for the first sheet
00300       if ( !first || ( expDialog && expDialog->printAlwaysSheetDelimiter() ) )
00301       {
00302         if ( !first)
00303           str += m_eol;
00304 
00305     QString name;
00306     if (expDialog)
00307       name = expDialog->getSheetDelimiter();
00308     else
00309       name = "********<SHEETNAME>********";
00310         const QString tname( i18n("<SHEETNAME>") );
00311         int pos = name.find( tname );
00312         if ( pos != -1 )
00313         {
00314           name.replace( pos, tname.length(), sheet->sheetName() );
00315         }
00316         str += name;
00317         str += m_eol;
00318         str += m_eol;
00319       }
00320 
00321       first = false;
00322 
00323 
00324       // this is just a bad approximation which fails for documents with less than 50 rows, but
00325       // we don't need any progress stuff there anyway :) (Werner)
00326       int value = 0;
00327       int step  = CSVMaxRow > 50 ? CSVMaxRow/50 : 1;
00328 
00329       // Print the CSV for the sheet data
00330       for ( int row = 1, i = 1 ; row <= CSVMaxRow ; ++row, ++i )
00331       {
00332         if ( i > step )
00333         {
00334           value += 2;
00335           emit sigProgress(value);
00336           i = 0;
00337         }
00338 
00339         QString collect;  // buffer delimiters while reading empty cells
00340 
00341         for ( int col = 1 ; col <= CSVMaxCol ; col++ )
00342         {
00343           const QString txt = exportCSVCell( sheet, col, row, textQuote, csvDelimiter );
00344 
00345           // if we encounter a non-empty cell, commit the buffered delimiters
00346       if (!txt.isEmpty()) {
00347         str += collect + txt;
00348         collect = QString();
00349       }
00350 
00351           collect += csvDelimiter;
00352         }
00353         // Here, throw away buffered delimiters. They're trailing and therefore
00354     // superfluous.
00355 
00356         str += m_eol;
00357       }
00358     }
00359   }
00360 
00361   emit sigProgress(100);
00362 
00363   QFile out(m_chain->outputFile());
00364   if ( !out.open( IO_WriteOnly ) )
00365   {
00366     kdError(30501) << "Unable to open output file!" << endl;
00367     out.close();
00368     delete expDialog;
00369     return KoFilter::StupidError;
00370   }
00371 
00372   QTextStream outStream( &out );
00373   outStream.setCodec( codec );
00374 
00375   outStream << str;
00376 
00377   out.close();
00378   delete expDialog;
00379   return KoFilter::OK;
00380 }
00381 
00382 #include <csvexport.moc>
KDE Home | KDE Accessibility Home | Description of Access Keys