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 )
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   if ( !text.isEmpty() )
00093   {
00094     if ( text.find( textQuote ) != -1 )
00095     {
00096       QString doubleTextQuote(textQuote);
00097       doubleTextQuote.append(textQuote);
00098       text.replace(textQuote, doubleTextQuote);
00099     }
00100 
00101     text.prepend(textQuote);
00102     text.append(textQuote);
00103   }
00104 
00105   return text;
00106 }
00107 
00108 // The reason why we use the KoDocument* approach and not the QDomDocument
00109 // approach is because we don't want to export formulas but values !
00110 KoFilter::ConversionStatus CSVExport::convert( const QCString & from, const QCString & to )
00111 {
00112   kdDebug(30501) << "CSVExport::convert" << endl;
00113   KoDocument* document = m_chain->inputDocument();
00114 
00115   if ( !document )
00116     return KoFilter::StupidError;
00117 
00118   if ( !::qt_cast<const KSpread::Doc *>( document ) )
00119   {
00120     kdWarning(30501) << "document isn't a KSpread::Doc but a " << document->className() << endl;
00121     return KoFilter::NotImplemented;
00122   }
00123   if ( ( to != "text/x-csv" && to != "text/plain" ) || from != "application/x-kspread" )
00124   {
00125     kdWarning(30501) << "Invalid mimetypes " << to << " " << from << endl;
00126     return KoFilter::NotImplemented;
00127   }
00128 
00129   Doc const * const ksdoc = static_cast<const Doc *>(document);
00130 
00131   if ( ksdoc->mimeType() != "application/x-kspread" )
00132   {
00133     kdWarning(30501) << "Invalid document mimetype " << ksdoc->mimeType() << endl;
00134     return KoFilter::NotImplemented;
00135   }
00136 
00137   CSVExportDialog *expDialog = 0;
00138   if (!m_chain->manager()->getBatchMode())
00139   {
00140     expDialog= new CSVExportDialog( 0 );
00141 
00142     if (!expDialog)
00143     {
00144       kdError(30501) << "Dialog has not been created! Aborting!" << endl;
00145       return KoFilter::StupidError;
00146     }
00147     expDialog->fillSheet( ksdoc->map() );
00148 
00149     if ( !expDialog->exec() )
00150     {
00151       delete expDialog;
00152       return KoFilter::UserCancelled;
00153     }
00154   }
00155 
00156   QTextCodec* codec = 0;
00157   QChar csvDelimiter;
00158   if (expDialog)
00159   {
00160     codec = expDialog->getCodec();
00161     if ( !codec )
00162     {
00163       delete expDialog;
00164       return KoFilter::StupidError;
00165     }
00166     csvDelimiter = expDialog->getDelimiter();
00167   }
00168   else
00169   {
00170     codec = QTextCodec::codecForName("UTF-8");
00171     csvDelimiter = ',';
00172   }
00173 
00174 
00175   // Now get hold of the sheet to export
00176   // (Hey, this could be part of the dialog too, choosing which sheet to export....
00177   //  It's great to have parametrable filters... IIRC even MSOffice doesn't have that)
00178   // Ok, for now we'll use the first sheet - my document has only one sheet anyway ;-)))
00179 
00180   bool first = true;
00181   QString str;
00182   QChar textQuote;
00183   if (expDialog)
00184     textQuote = expDialog->getTextQuote();
00185   else
00186     textQuote = '"';
00187 
00188   if ( expDialog && expDialog->exportSelectionOnly() )
00189   {
00190     kdDebug(30501) << "Export as selection mode" << endl;
00191     View const * const view = static_cast<View*>(ksdoc->views().getFirst());
00192 
00193     if ( !view ) // no view if embedded document
00194     {
00195       delete expDialog;
00196       return KoFilter::StupidError;
00197     }
00198 
00199     Sheet const * const sheet = view->activeSheet();
00200 
00201     QRect selection = view->selectionInfo()->lastRange();
00202     // Compute the highest row and column indexes (within the selection)
00203     // containing non-empty cells, respectively called CSVMaxRow CSVMaxCol.
00204     // The CSV will have CSVMaxRow rows, all with CSVMaxCol columns
00205     int right       = selection.right();
00206     int bottom      = selection.bottom();
00207     int CSVMaxRow   = 0;
00208     int CSVMaxCol   = 0;
00209 
00210     for ( int idxRow = 1, row = selection.top(); row <= bottom; ++row, ++idxRow )
00211     {
00212       for ( int idxCol = 1, col = selection.left(); col <= right; ++col, ++idxCol )
00213       {
00214         if( ! sheet->cellAt( col, row )->isEmpty() )
00215         {
00216           if ( idxRow > CSVMaxRow )
00217             CSVMaxRow = idxRow;
00218 
00219           if ( idxCol > CSVMaxCol )
00220             CSVMaxCol = idxCol;
00221         }
00222       }
00223     }
00224 
00225     for ( int idxRow = 1, row = selection.top();
00226           row <= bottom && idxRow <= CSVMaxRow; ++row, ++idxRow )
00227     {
00228       int idxCol = 1;
00229       for ( int col = selection.left();
00230             col <= right && idxCol <= CSVMaxCol; ++col, ++idxCol )
00231       {
00232         str += exportCSVCell( sheet, col, row, textQuote );
00233 
00234         if ( idxCol < CSVMaxCol )
00235           str += csvDelimiter;
00236       }
00237 
00238       // This is to deal with the case of non-rectangular selections 
00239       for ( ; idxCol < CSVMaxCol; ++idxCol )
00240           str += csvDelimiter;
00241 
00242       str += m_eol;
00243     }
00244   }
00245   else
00246   {
00247     kdDebug(30501) << "Export as full mode" << endl;
00248     QPtrListIterator<Sheet> it( ksdoc->map()->sheetList() );
00249     for( ; it.current(); ++it )
00250     {
00251       Sheet const * const sheet = it.current();
00252 
00253       if (expDialog && !expDialog->exportSheet( sheet->sheetName() ) )
00254       {
00255         continue;
00256       }
00257 
00258       // Compute the highest row and column indexes containing non-empty cells,
00259       // respectively called CSVMaxRow CSVMaxCol.
00260       // The CSV will have CSVMaxRow rows, all with CSVMaxCol columns
00261       int sheetMaxRow = sheet->maxRow();
00262       int sheetMaxCol = sheet->maxColumn();
00263       int CSVMaxRow   = 0;
00264       int CSVMaxCol   = 0;
00265 
00266       for ( int row = 1 ; row <= sheetMaxRow ; ++row)
00267       {
00268         for ( int col = 1 ; col <= sheetMaxCol ; col++ )
00269         {
00270           if( ! sheet->cellAt( col, row )->isEmpty() )
00271           {
00272             if ( row > CSVMaxRow )
00273               CSVMaxRow = row;
00274 
00275             if ( col > CSVMaxCol )
00276               CSVMaxCol = col;
00277           }
00278         }
00279       }
00280 
00281       // Skip the sheet altogether if it is empty
00282       if ( CSVMaxRow + CSVMaxCol == 0)
00283         continue;
00284 
00285       kdDebug(30501) << "Max row x column: " << CSVMaxRow << " x " << CSVMaxCol << endl;
00286 
00287       // Print sheet separators, except for the first sheet
00288       if ( !first || ( expDialog && expDialog->printAlwaysSheetDelimiter() ) )
00289       {
00290         if ( !first)
00291           str += m_eol;
00292 
00293     QString name;
00294     if (expDialog)
00295       name = expDialog->getSheetDelimiter();
00296     else
00297       name = "********<SHEETNAME>********";
00298         const QString tname( i18n("<SHEETNAME>") );
00299         int pos = name.find( tname );
00300         if ( pos != -1 )
00301         {
00302           name.replace( pos, tname.length(), sheet->sheetName() );
00303         }
00304         str += name;
00305         str += m_eol;
00306         str += m_eol;
00307       }
00308 
00309       first = false;
00310 
00311 
00312       // this is just a bad approximation which fails for documents with less than 50 rows, but
00313       // we don't need any progress stuff there anyway :) (Werner)
00314       int value = 0;
00315       int step  = CSVMaxRow > 50 ? CSVMaxRow/50 : 1;
00316 
00317       // Print the CSV for the sheet data
00318       for ( int row = 1, i = 1 ; row <= CSVMaxRow ; ++row, ++i )
00319       {
00320         if ( i > step )
00321         {
00322           value += 2;
00323           emit sigProgress(value);
00324           i = 0;
00325         }
00326 
00327         for ( int col = 1 ; col <= CSVMaxCol ; col++ )
00328         {
00329           str += exportCSVCell( sheet, col, row, textQuote );
00330 
00331           if ( col < CSVMaxCol )
00332             str += csvDelimiter;
00333         }
00334 
00335         str += m_eol;
00336       }
00337     }
00338   }
00339 
00340   emit sigProgress(100);
00341 
00342   QFile out(m_chain->outputFile());
00343   if ( !out.open( IO_WriteOnly ) )
00344   {
00345     kdError(30501) << "Unable to open output file!" << endl;
00346     out.close();
00347     delete expDialog;
00348     return KoFilter::StupidError;
00349   }
00350 
00351   QTextStream outStream( &out );
00352   outStream.setCodec( codec );
00353 
00354   outStream << str;
00355 
00356   out.close();
00357   delete expDialog;
00358   return KoFilter::OK;
00359 }
00360 
00361 #include <csvexport.moc>
KDE Home | KDE Accessibility Home | Description of Access Keys