/* This file is part of the KDE project Copyright (C) 2001 Eva Brucherseifer Copyright (C) 2005 Bram Schoenmakers based on kspread csv export filter by David Faure 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 using namespace KSpread; typedef KGenericFactory HTMLExportFactory; K_EXPORT_COMPONENT_FACTORY( libkspreadhtmlexport, HTMLExportFactory( "kofficefilters" ) ) const TQString html_table_tag = "table"; const TQString html_table_options = TQString(" border=\"%1\" cellspacing=\"%2\""); const TQString html_row_tag = "tr"; const TQString html_row_options = ""; const TQString html_cell_tag = "td"; const TQString html_cell_options = ""; const TQString html_bold = "b"; const TQString html_italic = "i"; const TQString html_underline = "u"; const TQString html_right= "right"; const TQString html_left= "left"; const TQString html_center= "center"; const TQString html_top="top"; const TQString html_bottom="bottom"; const TQString html_middle="middle"; const TQString html_h1="h1"; HTMLExport::HTMLExport(KoFilter *, const char *, const TQStringList&) : KoFilter(), m_dialog( new ExportDialog() ) { } HTMLExport::~HTMLExport() { delete m_dialog; } // HTML enitities, AFAIK we don't need to escape " to " (dnaber): const TQString strAmp ("&"); const TQString nbsp (" "); const TQString strLt ("<"); const TQString strGt (">"); // 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 HTMLExport::convert( const TQCString& from, const TQCString& to ) { if(to!="text/html" || from!="application/x-kspread") { kdWarning(30501) << "Invalid mimetypes " << to << " " << from << endl; return KoFilter::NotImplemented; } KoDocument* document = m_chain->inputDocument(); if ( !document ) return KoFilter::StupidError; if( !::tqt_cast( document ) ) // it's safer that way :) { kdWarning(30501) << "document isn't a KSpread::Doc but a " << document->className() << endl; return KoFilter::NotImplemented; } const Doc * ksdoc=static_cast(document); if( ksdoc->mimeType() != "application/x-kspread" ) { kdWarning(30501) << "Invalid document mimetype " << ksdoc->mimeType() << endl; return KoFilter::NotImplemented; } Sheet *sheet = ksdoc->map()->firstSheet(); TQString filenameBase = m_chain->outputFile(); filenameBase = filenameBase.left( filenameBase.findRev( '.' ) ); TQStringList sheets; while( sheet != 0 ) { int rows = 0; int columns = 0; detectFilledCells( sheet, rows, columns ); m_rowmap[ sheet->sheetName() ] = rows; m_columnmap[ sheet->sheetName() ] = columns; if( rows > 0 && columns > 0 ) { sheets.append( sheet->sheetName() ); } sheet = ksdoc->map()->nextSheet(); } m_dialog->setSheets( sheets ); if( m_dialog->exec() == TQDialog::Rejected ) return KoFilter::UserCancelled; sheets = m_dialog->sheets(); TQString str; for( uint i = 0; i < sheets.count() ; ++i ) { sheet = ksdoc->map()->findSheet( sheets[i] ); TQString file = fileName( filenameBase, sheet->sheetName(), sheets.count() > 1 ); if( m_dialog->separateFiles() || sheets[i] == sheets.first() ) { str = TQString(); openPage( sheet, document, str ); writeTOC( sheets, filenameBase, str ); } convertSheet( sheet, str, m_rowmap[ sheet->sheetName() ], m_columnmap[ sheet->sheetName() ] ); if( m_dialog->separateFiles() || sheets[i] == sheets.last() ) { closePage( str ); TQFile out(file); if(!out.open(IO_WriteOnly)) { kdError(30501) << "Unable to open output file!" << endl; out.close(); return KoFilter::FileNotFound; } TQTextStream streamOut(&out); streamOut.setCodec( m_dialog->encoding() ); streamOut << str << endl; out.close(); } if( !m_dialog->separateFiles() ) { createSheetSeparator( str ); } } emit sigProgress(100); return KoFilter::OK; } void HTMLExport::openPage( Sheet *sheet, KoDocument *document, TQString &str ) { TQString title; KoDocumentInfo *info = document->documentInfo(); KoDocumentInfoAbout *aboutPage = static_cast(info->page( "about" )); if ( aboutPage && !aboutPage->title().isEmpty() ) title = aboutPage->title() + " - "; title += sheet->sheetName(); // header str = " \n"; str += "\n"; str += "\n"; str += "\n").arg( m_dialog->encoding()->mimeName() ); str += "\n"; // Insert stylesheet if( !m_dialog->customStyleURL().isEmpty() ) { str += "customStyleURL(); str += "\" title=\"Style\" >\n"; } str += "" + title + "\n"; str += "\n"; str += TQString("\n").arg( sheet->isRightToLeft()?"rtl":"ltr"); str += "\n"; } void HTMLExport::closePage( TQString &str ) { str += "

" + i18n("Top") + "

\n"; str += "\n"; str += "\n\n"; } void HTMLExport::convertSheet( Sheet *sheet, TQString &str, int iMaxUsedRow, int iMaxUsedColumn ) { TQString emptyLines; // Either we get hold of KSpreadTable::m_dctCells and apply the old method below (for sorting) // or, cleaner and already sorted, we use KSpreadTable's API (slower probably, though) int iMaxRow = sheet->maxRow(); if( !m_dialog->separateFiles() ) str += "sheetName().lower().stripWhiteSpace() + "\">\n"; str += ("

" + sheet->sheetName() + "


\n"); // 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=iMaxRow > 50 ? iMaxRow/50 : 1; int i=1; str += "<" + html_table_tag + html_table_options.arg( m_dialog->useBorders() ? "1" : "0" ).arg( m_dialog->pixelsBetweenCells() ) + TQString("dir=\"%1\">\n").arg(sheet->isRightToLeft()?"rtl":"ltr"); unsigned int nonempty_cells_prev=0; for ( int currentrow = 1 ; currentrow <= iMaxUsedRow ; ++currentrow, ++i ) { if(i>step) { value+=2; emit sigProgress(value); i=0; } TQString separators; TQString line; unsigned int nonempty_cells=0; unsigned int colspan_cells=0; for ( int currentcolumn = 1 ; currentcolumn <= iMaxUsedColumn ; currentcolumn++ ) { Cell * cell = sheet->cellAt( currentcolumn, currentrow, false ); colspan_cells=cell->extraXCells(); if (cell->needsPrinting()) nonempty_cells++; TQString text; TQColor bgcolor = cell->bgColor(currentcolumn,currentrow); // FIXME: some formatting seems to be missing with cell->text(), e.g. // "208.00" in KSpread will be "208" in HTML (not always?!) bool link = false; if ( !cell->link().isEmpty() ) { if ( localReferenceAnchor(cell->link()) ) { text = cell->text(); } else { text = "
link() + "\">" + cell->text() + ""; link = true; } } else text=cell->strOutText(); #if 0 switch( cell->content() ) { case Cell::Text: text = cell->text(); break; case Cell::RichText: case Cell::VisualFormula: text = cell->text(); // untested break; case Cell::Formula: cell->calc( TRUE ); // Incredible, cells are not calculated if the document was just opened text = cell->valueString(); break; } text = cell->prefix(currentrow, currentcolumn) + " " + text + " " + cell->postfix(currentrow, currentcolumn); #endif line += " <" + html_cell_tag + html_cell_options; if (text.isRightToLeft() != sheet->isRightToLeft()) line += TQString(" dir=\"%1\" ").arg(text.isRightToLeft()?"rtl":"ltr"); if (bgcolor.isValid() && bgcolor.name()!="#ffffff") // change color only for non-white cells line += " bgcolor=\"" + bgcolor.name() + "\""; switch((Format::Align)cell->defineAlignX()) { case Format::Left: line+=" align=\"" + html_left +"\""; break; case Format::Right: line+=" align=\"" + html_right +"\""; break; case Format::Center: line+=" align=\"" + html_center +"\""; break; case Format::Undefined: break; } switch((Format::AlignY)cell-> format()->alignY(currentrow, currentcolumn)) { case Format::Top: line+=" valign=\"" + html_top +"\""; break; case Format::Middle: line+=" valign=\"" + html_middle +"\""; break; case Format::Bottom: line+=" valign=\"" + html_bottom +"\""; break; case Format::UndefinedY: break; } line+=" width=\""+TQString::number(cell->width())+"\""; line+=" height=\""+TQString::number(cell->height())+"\""; if (cell->extraXCells()>0) { TQString tmp; int extra_cells=cell->extraXCells(); line += " colspan=\"" + tmp.setNum(extra_cells+1) + "\""; currentcolumn += extra_cells; } text = text.stripWhiteSpace(); if( text.at(0) == '!' ) { // this is supposed to be markup, just remove the '!': text = text.right(text.length()-1); } else if ( !link ) { // Escape HTML characters. text.replace ('&' , strAmp) .replace ('<' , strLt) .replace ('>' , strGt) .replace (' ' , nbsp); } line += ">\n"; if (cell->format()->textFontBold(currentcolumn,currentrow)) { text.insert(0, "<" + html_bold + ">"); text.append(""); } if (cell->format()->textFontItalic(currentcolumn,currentrow)) { text.insert(0, "<" + html_italic + ">"); text.append(""); } if (cell->format()->textFontUnderline(currentcolumn,currentrow)) { text.insert(0, "<" + html_underline + ">"); text.append(""); } TQColor textColor = cell->format()->textColor(currentcolumn,currentrow); if (textColor.isValid() && textColor.name()!="#000000") // change color only for non-default text { text.insert(0, ""); text.append(""); } line += " " + text; line += "\n \n"; } if (nonempty_cells == 0 && nonempty_cells_prev == 0) { nonempty_cells_prev = nonempty_cells; // skip line if there's more than one empty line continue; } else { nonempty_cells_prev = nonempty_cells; str += emptyLines; str += "<" + html_row_tag + html_row_options + ">\n"; str += line; str += ""; emptyLines = TQString(); // Append a CR, but in a temp string -> if no other real line, // then those will be dropped emptyLines += "\n"; } } str += "\n\n
\n"; } void HTMLExport::createSheetSeparator( TQString &str ) { str += ("

" + i18n("Top") + "

\n" ); str += "
\n"; } void HTMLExport::writeTOC( const TQStringList &sheets, const TQString &base, TQString &str ) { // don't create TOC for 1 sheet if( sheets.count() == 1 ) return; str += "

\n"; for( uint i = 0 ; i < sheets.count() ; ++i ) { str += "separateFiles() ) { str += fileName( base, sheets[i], sheets.count() > 1 ); } else { str += "#" + sheets[i].lower().stripWhiteSpace(); } str += "\">" + sheets[i] + "\n"; if( i != sheets.count() -1 ) str += " - "; } str += "


\n"; } TQString HTMLExport::fileName( const TQString &base, const TQString &sheetName, bool multipleFiles ) { TQString fileName = base; if( m_dialog->separateFiles() && multipleFiles ) { fileName += "-" + sheetName; } fileName += ".html"; return fileName; } void HTMLExport::detectFilledCells( Sheet *sheet, int &rows, int &columns ) { int iMaxColumn = sheet->maxColumn(); int iMaxRow = sheet->maxRow(); rows = 0; columns = 0; for ( int currentrow = 1 ; currentrow <= iMaxRow ; ++currentrow) { Cell * cell = 0L; int iUsedColumn=0; for ( int currentcolumn = 1 ; currentcolumn <= iMaxColumn ; currentcolumn++ ) { cell = sheet->cellAt( currentcolumn, currentrow, false ); TQString text; if ( !cell->isDefault() && !cell->isEmpty() ) { iUsedColumn = currentcolumn; } } if (cell) iUsedColumn += cell->extraXCells(); if (iUsedColumn > columns) columns = iUsedColumn; if ( iUsedColumn > 0 ) rows = currentrow; } } #include