You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
383 lines
10 KiB
383 lines
10 KiB
/* This file is part of the KDE project
|
|
Copyright (C) 2000 David Faure <faure@kde.org>
|
|
Copyright (C) 2004 Nicolas GOUTTE <goutte@kde.org>
|
|
|
|
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 <csvexport.h>
|
|
|
|
#include <tqfile.h>
|
|
#include <tqtextcodec.h>
|
|
|
|
#include <kdebug.h>
|
|
#include <kmessagebox.h>
|
|
#include <kgenericfactory.h>
|
|
#include <KoFilterChain.h>
|
|
#include <KoFilterManager.h>
|
|
|
|
#include <kspread_map.h>
|
|
#include <kspread_sheet.h>
|
|
#include <kspread_doc.h>
|
|
#include <kspread_view.h>
|
|
#include <selection.h>
|
|
|
|
#include <csvexportdialog.h>
|
|
|
|
using namespace KSpread;
|
|
|
|
typedef KGenericFactory<CSVExport, KoFilter> 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.tqfind( textQuote ) != -1 )
|
|
{
|
|
TQString doubleTextQuote(textQuote);
|
|
doubleTextQuote.append(textQuote);
|
|
text.tqreplace(textQuote, doubleTextQuote);
|
|
quote = true;
|
|
|
|
} else if ( text[0].isSpace() || text[text.length()-1].isSpace() )
|
|
quote = true;
|
|
else if ( text.tqfind( csvDelimiter ) != -1 )
|
|
quote = true;
|
|
else if ( text.tqfind( "\n" ) != -1 || text.tqfind( "\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::ConversiontqStatus CSVExport::convert( const TQCString & from, const TQCString & to )
|
|
{
|
|
kdDebug(30501) << "CSVExport::convert" << endl;
|
|
KoDocument* document = m_chain->inputDocument();
|
|
|
|
if ( !document )
|
|
return KoFilter::StupidError;
|
|
|
|
if ( !::tqqt_cast<const KSpread::Doc *>( 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<const Doc *>(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<View*>(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<Sheet> 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 = "********<SHEETNAME>********";
|
|
const TQString tname( i18n("<SHEETNAME>") );
|
|
int pos = name.tqfind( tname );
|
|
if ( pos != -1 )
|
|
{
|
|
name.tqreplace( 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 <csvexport.moc>
|