/* This file is part of the KDE project Copyright (C) 2000 David Faure Copyright (C) 2004 Nicolas GOUTTE This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace KSpread; typedef KGenericFactory CSVExportFactory; K_EXPORT_COMPONENT_FACTORY( libcsvexport, CSVExportFactory( "kofficefilters" ) ) class Cell { public: int row, col; TQString text; bool operator < ( const Cell & c ) const { return row < c.row || ( row == c.row && col < c.col ); } bool operator == ( const Cell & c ) const { return row == c.row && col == c.col; } }; CSVExport::CSVExport( KoFilter *, const char *, const TQStringList & ) : KoFilter(), m_eol("\n") { } TQString CSVExport::exportCSVCell( Sheet const * const sheet, int col, int row, TQChar const & textQuote, TQChar csvDelimiter ) { // This function, given a cell, returns a string corresponding to its export in CSV format // It proceeds by: // - getting the value of the cell, if any // - protecting quote characters within cells, if any // - enclosing the cell in quotes if the cell is non empty KSpread::Cell const * const cell = sheet->cellAt( col, row ); TQString text; if ( !cell->isDefault() && !cell->isEmpty() ) { if ( cell->isFormula() ) text = cell->strOutText(); else if ( !cell->link().isEmpty() ) text = cell->text(); // untested else if( cell->isTime() ) text = cell->value().asTime().toString("hh:mm:ss"); else if( cell->isDate() ) text = cell->value().asDate().toString("yyyy-MM-dd"); else text = cell->strOutText(); } // quote only when needed (try to mimic excel) bool quote = false; if ( !text.isEmpty() ) { if ( text.find( textQuote ) != -1 ) { TQString doubleTextQuote(textQuote); doubleTextQuote.append(textQuote); text.replace(textQuote, doubleTextQuote); quote = true; } else if ( text[0].isSpace() || text[text.length()-1].isSpace() ) quote = true; else if ( text.find( csvDelimiter ) != -1 ) quote = true; else if ( text.find( "\n" ) != -1 || text.find( "\r" ) != -1 ) quote = true; } if ( quote ) { text.prepend(textQuote); text.append(textQuote); } return text; } // The reason why we use the KoDocument* approach and not the TQDomDocument // approach is because we don't want to export formulas but values ! KoFilter::ConversionStatus CSVExport::convert( const TQCString & from, const TQCString & to ) { kdDebug(30501) << "CSVExport::convert" << endl; KoDocument* document = m_chain->inputDocument(); if ( !document ) return KoFilter::StupidError; if ( !::tqt_cast( document ) ) { kdWarning(30501) << "document isn't a KSpread::Doc but a " << document->className() << endl; return KoFilter::NotImplemented; } if ( ( to != "text/x-csv" && to != "text/plain" ) || from != "application/x-kspread" ) { kdWarning(30501) << "Invalid mimetypes " << to << " " << from << endl; return KoFilter::NotImplemented; } Doc const * const ksdoc = static_cast(document); if ( ksdoc->mimeType() != "application/x-kspread" ) { kdWarning(30501) << "Invalid document mimetype " << ksdoc->mimeType() << endl; return KoFilter::NotImplemented; } CSVExportDialog *expDialog = 0; if (!m_chain->manager()->getBatchMode()) { expDialog= new CSVExportDialog( 0 ); if (!expDialog) { kdError(30501) << "Dialog has not been created! Aborting!" << endl; return KoFilter::StupidError; } expDialog->fillSheet( ksdoc->map() ); if ( !expDialog->exec() ) { delete expDialog; return KoFilter::UserCancelled; } } TQTextCodec* codec = 0; TQChar csvDelimiter; if (expDialog) { codec = expDialog->getCodec(); if ( !codec ) { delete expDialog; return KoFilter::StupidError; } csvDelimiter = expDialog->getDelimiter(); m_eol = expDialog->getEndOfLine(); } else { codec = TQTextCodec::codecForName("UTF-8"); csvDelimiter = ','; } // Now get hold of the sheet to export // (Hey, this could be part of the dialog too, choosing which sheet to export.... // It's great to have parametrable filters... IIRC even MSOffice doesn't have that) // Ok, for now we'll use the first sheet - my document has only one sheet anyway ;-))) bool first = true; TQString str; TQChar textQuote; if (expDialog) textQuote = expDialog->getTextQuote(); else textQuote = '"'; if ( expDialog && expDialog->exportSelectionOnly() ) { kdDebug(30501) << "Export as selection mode" << endl; View const * const view = static_cast(ksdoc->views().getFirst()); if ( !view ) // no view if embedded document { delete expDialog; return KoFilter::StupidError; } Sheet const * const sheet = view->activeSheet(); TQRect selection = view->selectionInfo()->lastRange(); // Compute the highest row and column indexes (within the selection) // containing non-empty cells, respectively called CSVMaxRow CSVMaxCol. // The CSV will have CSVMaxRow rows, all with CSVMaxCol columns int right = selection.right(); int bottom = selection.bottom(); int CSVMaxRow = 0; int CSVMaxCol = 0; for ( int idxRow = 1, row = selection.top(); row <= bottom; ++row, ++idxRow ) { for ( int idxCol = 1, col = selection.left(); col <= right; ++col, ++idxCol ) { if( ! sheet->cellAt( col, row )->isEmpty() ) { if ( idxRow > CSVMaxRow ) CSVMaxRow = idxRow; if ( idxCol > CSVMaxCol ) CSVMaxCol = idxCol; } } } for ( int idxRow = 1, row = selection.top(); row <= bottom && idxRow <= CSVMaxRow; ++row, ++idxRow ) { int idxCol = 1; for ( int col = selection.left(); col <= right && idxCol <= CSVMaxCol; ++col, ++idxCol ) { str += exportCSVCell( sheet, col, row, textQuote, csvDelimiter ); if ( idxCol < CSVMaxCol ) str += csvDelimiter; } // This is to deal with the case of non-rectangular selections for ( ; idxCol < CSVMaxCol; ++idxCol ) str += csvDelimiter; str += m_eol; } } else { kdDebug(30501) << "Export as full mode" << endl; TQPtrListIterator it( ksdoc->map()->sheetList() ); for( ; it.current(); ++it ) { Sheet const * const sheet = it.current(); if (expDialog && !expDialog->exportSheet( sheet->sheetName() ) ) { continue; } // Compute the highest row and column indexes containing non-empty cells, // respectively called CSVMaxRow CSVMaxCol. // The CSV will have CSVMaxRow rows, all with CSVMaxCol columns int sheetMaxRow = sheet->maxRow(); int sheetMaxCol = sheet->maxColumn(); int CSVMaxRow = 0; int CSVMaxCol = 0; for ( int row = 1 ; row <= sheetMaxRow ; ++row) { for ( int col = 1 ; col <= sheetMaxCol ; col++ ) { if( ! sheet->cellAt( col, row )->isEmpty() ) { if ( row > CSVMaxRow ) CSVMaxRow = row; if ( col > CSVMaxCol ) CSVMaxCol = col; } } } // Skip the sheet altogether if it is empty if ( CSVMaxRow + CSVMaxCol == 0) continue; kdDebug(30501) << "Max row x column: " << CSVMaxRow << " x " << CSVMaxCol << endl; // Print sheet separators, except for the first sheet if ( !first || ( expDialog && expDialog->printAlwaysSheetDelimiter() ) ) { if ( !first) str += m_eol; TQString name; if (expDialog) name = expDialog->getSheetDelimiter(); else name = "****************"; const TQString tname( i18n("") ); int pos = name.find( tname ); if ( pos != -1 ) { name.replace( pos, tname.length(), sheet->sheetName() ); } str += name; str += m_eol; str += m_eol; } first = false; // this is just a bad approximation which fails for documents with less than 50 rows, but // we don't need any progress stuff there anyway :) (Werner) int value = 0; int step = CSVMaxRow > 50 ? CSVMaxRow/50 : 1; // Print the CSV for the sheet data for ( int row = 1, i = 1 ; row <= CSVMaxRow ; ++row, ++i ) { if ( i > step ) { value += 2; emit sigProgress(value); i = 0; } TQString collect; // buffer delimiters while reading empty cells for ( int col = 1 ; col <= CSVMaxCol ; col++ ) { const TQString txt = exportCSVCell( sheet, col, row, textQuote, csvDelimiter ); // if we encounter a non-empty cell, commit the buffered delimiters if (!txt.isEmpty()) { str += collect + txt; collect = TQString(); } collect += csvDelimiter; } // Here, throw away buffered delimiters. They're trailing and therefore // superfluous. str += m_eol; } } } emit sigProgress(100); TQFile out(m_chain->outputFile()); if ( !out.open( IO_WriteOnly ) ) { kdError(30501) << "Unable to open output file!" << endl; out.close(); delete expDialog; return KoFilter::StupidError; } TQTextStream outStream( &out ); outStream.setCodec( codec ); outStream << str; out.close(); delete expDialog; return KoFilter::OK; } #include