filters

htmlexport.cc

00001 /* This file is part of the KDE project
00002    Copyright (C) 2001 Eva Brucherseifer <eva@kde.org>
00003    Copyright (C) 2005 Bram Schoenmakers <bramschoenmakers@kde.nl>
00004    based on kspread csv export filter by David Faure
00005 
00006    This library is free software; you can redistribute it and/or
00007    modify it under the terms of the GNU Library General Public
00008    License as published by the Free Software Foundation; either
00009    version 2 of the License, or (at your option) any later version.
00010 
00011    This library is distributed in the hope that it will be useful,
00012    but WITHOUT ANY WARRANTY; without even the implied warranty of
00013    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
00014    Library General Public License for more details.
00015 
00016    You should have received a copy of the GNU Library General Public License
00017    along with this library; see the file COPYING.LIB.  If not, write to
00018    the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
00019  * Boston, MA 02110-1301, USA.
00020 */
00021 
00022 #include <htmlexport.h>
00023 #include <exportdialog.h>
00024 
00025 #include <qfile.h>
00026 #include <qtextcodec.h>
00027 
00028 #include <kdebug.h>
00029 #include <kgenericfactory.h>
00030 #include <KoFilterChain.h>
00031 #include <KoDocumentInfo.h>
00032 #include <kofficeversion.h>
00033 
00034 #include <kspread_map.h>
00035 #include <kspread_sheet.h>
00036 #include <kspread_doc.h>
00037 #include <kspread_util.h>
00038 
00039 using namespace KSpread;
00040 
00041 typedef KGenericFactory<HTMLExport, KoFilter> HTMLExportFactory;
00042 K_EXPORT_COMPONENT_FACTORY( libkspreadhtmlexport, HTMLExportFactory( "kofficefilters" ) )
00043 
00044 const QString html_table_tag = "table";
00045 const QString html_table_options = QString(" border=\"%1\" cellspacing=\"%2\"");
00046 const QString html_row_tag = "tr";
00047 const QString html_row_options = "";
00048 const QString html_cell_tag = "td";
00049 const QString html_cell_options = "";
00050 const QString html_bold = "b";
00051 const QString html_italic = "i";
00052 const QString html_underline = "u";
00053 const QString html_right= "right";
00054 const QString html_left= "left";
00055 const QString html_center= "center";
00056 const QString html_top="top";
00057 const QString html_bottom="bottom";
00058 const QString html_middle="middle";
00059 const QString html_h1="h1";
00060 
00061 HTMLExport::HTMLExport(KoFilter *, const char *, const QStringList&) :
00062     KoFilter(), m_dialog( new ExportDialog() )
00063 {
00064 }
00065 
00066 HTMLExport::~HTMLExport()
00067 {
00068   delete m_dialog;
00069 }
00070 
00071 // HTML enitities, AFAIK we don't need to escape " to &quot; (dnaber):
00072 const QString strAmp ("&amp;");
00073 const QString nbsp ("&nbsp;");
00074 const QString strLt  ("&lt;");
00075 const QString strGt  ("&gt;");
00076 
00077 // The reason why we use the KoDocument* approach and not the QDomDocument
00078 // approach is because we don't want to export formulas but values !
00079 KoFilter::ConversionStatus HTMLExport::convert( const QCString& from, const QCString& to )
00080 {
00081     if(to!="text/html" || from!="application/x-kspread")
00082     {
00083       kdWarning(30501) << "Invalid mimetypes " << to << " " << from << endl;
00084       return KoFilter::NotImplemented;
00085     }
00086 
00087     KoDocument* document = m_chain->inputDocument();
00088 
00089     if ( !document )
00090       return KoFilter::StupidError;
00091 
00092     if( !::qt_cast<const KSpread::Doc *>( document ) )  // it's safer that way :)
00093     {
00094       kdWarning(30501) << "document isn't a KSpread::Doc but a " << document->className() << endl;
00095       return KoFilter::NotImplemented;
00096     }
00097 
00098     const Doc * ksdoc=static_cast<const Doc *>(document);
00099 
00100     if( ksdoc->mimeType() != "application/x-kspread" )
00101     {
00102       kdWarning(30501) << "Invalid document mimetype " << ksdoc->mimeType() << endl;
00103       return KoFilter::NotImplemented;
00104     }
00105 
00106     Sheet *sheet = ksdoc->map()->firstSheet();
00107     QString filenameBase = m_chain->outputFile();
00108     filenameBase = filenameBase.left( filenameBase.findRev( '.' ) );
00109 
00110     QStringList sheets;
00111     while( sheet != 0 )
00112     {
00113       int rows = 0;
00114       int columns = 0;
00115       detectFilledCells( sheet, rows, columns );
00116       m_rowmap[ sheet->sheetName() ] = rows;
00117       m_columnmap[ sheet->sheetName() ] = columns;
00118 
00119       if( rows > 0 && columns > 0 )
00120       {
00121         sheets.append( sheet->sheetName() );
00122       }
00123       sheet = ksdoc->map()->nextSheet();
00124     }
00125     m_dialog->setSheets( sheets );
00126 
00127     if( m_dialog->exec() == QDialog::Rejected )
00128       return KoFilter::UserCancelled;
00129 
00130     sheets = m_dialog->sheets();
00131     QString str;
00132     for( uint i = 0; i < sheets.count() ; ++i )
00133     {
00134       sheet = ksdoc->map()->findSheet( sheets[i] );
00135 
00136       QString file = fileName( filenameBase, sheet->sheetName(), sheets.count() > 1 );
00137 
00138       if( m_dialog->separateFiles() || sheets[i] == sheets.first() )
00139       {
00140         str = QString::null;
00141         openPage( sheet, document, str );
00142         writeTOC( sheets, filenameBase, str );
00143       }
00144 
00145       convertSheet( sheet, str, m_rowmap[ sheet->sheetName() ], m_columnmap[ sheet->sheetName() ] );
00146 
00147       if( m_dialog->separateFiles() || sheets[i] == sheets.last() )
00148       {
00149         closePage( str );
00150         QFile out(file);
00151         if(!out.open(IO_WriteOnly)) {
00152           kdError(30501) << "Unable to open output file!" << endl;
00153           out.close();
00154           return KoFilter::FileNotFound;
00155         }
00156         QTextStream streamOut(&out);
00157         streamOut.setCodec( m_dialog->encoding() );
00158         streamOut << str << endl;
00159         out.close();
00160       }
00161 
00162       if( !m_dialog->separateFiles() )
00163       {
00164         createSheetSeparator( str );
00165       }
00166 
00167     }
00168 
00169     emit sigProgress(100);
00170     return KoFilter::OK;
00171 }
00172 
00173 void HTMLExport::openPage( Sheet *sheet, KoDocument *document, QString &str )
00174 {
00175   QString title;
00176   KoDocumentInfo *info = document->documentInfo();
00177   KoDocumentInfoAbout *aboutPage = static_cast<KoDocumentInfoAbout *>(info->page( "about" ));
00178   if ( aboutPage && !aboutPage->title().isEmpty() )
00179     title = aboutPage->title() + " - ";
00180 
00181   title += sheet->sheetName();
00182 
00183       // header
00184   str = "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\" ";
00185   str += " \"http://www.w3.org/TR/html4/loose.dtd\"> \n";
00186   str += "<html>\n";
00187   str += "<head>\n";
00188   str += "<meta http-equiv=\"Content-Type\" ";
00189   str += QString("content=\"text/html; charset=%1\">\n").arg( m_dialog->encoding()->mimeName() );
00190   str += "<meta name=\"Generator\" ";
00191   str += "content=\"KSpread HTML Export Filter Version = ";
00192   str += KOFFICE_VERSION_STRING;
00193   str += "\">\n";
00194 
00195     // Insert stylesheet
00196   if( !m_dialog->customStyleURL().isEmpty() )
00197   {
00198     str += "<link ref=\"stylesheet\" type=\"text/css\" href=\"";
00199     str += m_dialog->customStyleURL();
00200     str += "\" title=\"Style\" >\n";
00201   }
00202 
00203   str += "<title>" + title + "</title>\n";
00204   str += "</head>\n";
00205   str += QString("<body bgcolor=\"#FFFFFF\" dir=\"%1\">\n").arg(
00206       sheet->isRightToLeft()?"rtl":"ltr");
00207 
00208   str += "<a name=\"__top\">\n";
00209 }
00210 
00211 void HTMLExport::closePage( QString &str )
00212 {
00213   str += "<p align=\"" + html_center + "\"><a href=\"#__top\">" + i18n("Top") + "</a></p>\n";
00214   str += "</body>\n";
00215   str += "</html>\n\n";
00216 }
00217 
00218 void HTMLExport::convertSheet( Sheet *sheet, QString &str, int iMaxUsedRow, int iMaxUsedColumn )
00219 {
00220     QString emptyLines;
00221 
00222     // Either we get hold of KSpreadTable::m_dctCells and apply the old method below (for sorting)
00223     // or, cleaner and already sorted, we use KSpreadTable's API (slower probably, though)
00224     int iMaxRow = sheet->maxRow();
00225 
00226     if( !m_dialog->separateFiles() )
00227         str += "<a name=\"" + sheet->sheetName().lower().stripWhiteSpace() + "\">\n";
00228 
00229     str += ("<h1>" + sheet->sheetName() + "</h1><br>\n");
00230 
00231     // this is just a bad approximation which fails for documents with less than 50 rows, but
00232     // we don't need any progress stuff there anyway :) (Werner)
00233     int value=0;
00234     int step=iMaxRow > 50 ? iMaxRow/50 : 1;
00235     int i=1;
00236 
00237     str += "<" + html_table_tag + html_table_options.arg( m_dialog->useBorders() ? "1" : "0" ).arg( m_dialog->pixelsBetweenCells() ) +
00238            QString("dir=\"%1\">\n").arg(sheet->isRightToLeft()?"rtl":"ltr");
00239 
00240     unsigned int nonempty_cells_prev=0;
00241 
00242     for ( int currentrow = 1 ; currentrow <= iMaxUsedRow ; ++currentrow, ++i )
00243     {
00244         if(i>step) {
00245             value+=2;
00246             emit sigProgress(value);
00247             i=0;
00248         }
00249 
00250         QString separators;
00251         QString line;
00252         unsigned int nonempty_cells=0;
00253         unsigned int colspan_cells=0;
00254 
00255         for ( int currentcolumn = 1 ; currentcolumn <= iMaxUsedColumn ; currentcolumn++ )
00256         {
00257             Cell * cell = sheet->cellAt( currentcolumn, currentrow, false );
00258             colspan_cells=cell->extraXCells();
00259             if (cell->needsPrinting())
00260                 nonempty_cells++;
00261             QString text;
00262             QColor bgcolor = cell->bgColor(currentcolumn,currentrow);
00263             // FIXME: some formatting seems to be missing with cell->text(), e.g.
00264             // "208.00" in KSpread will be "208" in HTML (not always?!)
00265             bool link = false;
00266 
00267             if ( !cell->link().isEmpty() )
00268             {
00269                 if ( localReferenceAnchor(cell->link()) )
00270                 {
00271                     text = cell->text();
00272                 }
00273                 else
00274                 {
00275                     text = " <A href=\"" + cell->link() + "\">" + cell->text() + "</A>";
00276                     link = true;
00277                 }
00278             }
00279             else
00280                 text=cell->strOutText();
00281 #if 0
00282             switch( cell->content() ) {
00283             case Cell::Text:
00284                 text = cell->text();
00285                 break;
00286             case Cell::RichText:
00287             case Cell::VisualFormula:
00288                 text = cell->text(); // untested
00289                 break;
00290             case Cell::Formula:
00291                 cell->calc( TRUE ); // Incredible, cells are not calculated if the document was just opened
00292                 text = cell->valueString();
00293                 break;
00294             }
00295             text = cell->prefix(currentrow, currentcolumn) + " " + text + " "
00296                    + cell->postfix(currentrow, currentcolumn);
00297 #endif
00298             line += "  <" + html_cell_tag + html_cell_options;
00299         if (text.isRightToLeft() != sheet->isRightToLeft())
00300                 line += QString(" dir=\"%1\" ").arg(text.isRightToLeft()?"rtl":"ltr");
00301             if (bgcolor.isValid() && bgcolor.name()!="#ffffff") // change color only for non-white cells
00302                 line += " bgcolor=\"" + bgcolor.name() + "\"";
00303 
00304             switch((Format::Align)cell->defineAlignX())
00305             {
00306             case Format::Left:
00307                 line+=" align=\"" + html_left +"\"";
00308                 break;
00309             case Format::Right:
00310                 line+=" align=\"" + html_right +"\"";
00311                 break;
00312             case Format::Center:
00313                 line+=" align=\"" + html_center +"\"";
00314                 break;
00315             case Format::Undefined:
00316                 break;
00317             }
00318             switch((Format::AlignY)cell-> format()->alignY(currentrow, currentcolumn))
00319             {
00320             case Format::Top:
00321                 line+=" valign=\"" + html_top +"\"";
00322                 break;
00323             case Format::Middle:
00324                 line+=" valign=\"" + html_middle +"\"";
00325                 break;
00326             case Format::Bottom:
00327                 line+=" valign=\"" + html_bottom +"\"";
00328                 break;
00329             case Format::UndefinedY:
00330                 break;
00331             }
00332             line+=" width=\""+QString::number(cell->width())+"\"";
00333             line+=" height=\""+QString::number(cell->height())+"\"";
00334 
00335             if (cell->extraXCells()>0)
00336             {
00337                 QString tmp;
00338                 int extra_cells=cell->extraXCells();
00339                 line += " colspan=\"" + tmp.setNum(extra_cells+1) + "\"";
00340                 currentcolumn += extra_cells;
00341             }
00342             text = text.stripWhiteSpace();
00343             if( text.at(0) == '!' ) {
00344                 // this is supposed to be markup, just remove the '!':
00345                 text = text.right(text.length()-1);
00346             } else if ( !link ) {
00347                 // Escape HTML characters.
00348                 text.replace ('&' , strAmp)
00349                     .replace ('<' , strLt)
00350                     .replace ('>' , strGt)
00351                     .replace (' ' , nbsp);
00352             }
00353             line += ">\n";
00354 
00355             if (cell->format()->textFontBold(currentcolumn,currentrow))
00356             {
00357                 text.insert(0, "<" + html_bold + ">");
00358                 text.append("</" + html_bold + ">");
00359             }
00360             if (cell->format()->textFontItalic(currentcolumn,currentrow))
00361             {
00362                 text.insert(0, "<" + html_italic + ">");
00363                 text.append("</" + html_italic + ">");
00364             }
00365             if (cell->format()->textFontUnderline(currentcolumn,currentrow))
00366             {
00367                 text.insert(0, "<" + html_underline + ">");
00368                 text.append("</" + html_underline + ">");
00369             }
00370             QColor textColor = cell->format()->textColor(currentcolumn,currentrow);
00371             if (textColor.isValid() && textColor.name()!="#000000") // change color only for non-default text
00372             {
00373                 text.insert(0, "<font color=\"" + textColor.name() + "\">");
00374                 text.append("</font>");
00375             }
00376             line += "  " + text;
00377             line += "\n  </" + html_cell_tag + ">\n";
00378         }
00379 
00380         if (nonempty_cells == 0 && nonempty_cells_prev == 0) {
00381             nonempty_cells_prev = nonempty_cells;
00382             // skip line if there's more than one empty line
00383             continue;
00384         } else {
00385             nonempty_cells_prev = nonempty_cells;
00386             str += emptyLines;
00387             str += "<" + html_row_tag + html_row_options + ">\n";
00388             str += line;
00389             str += "</" + html_row_tag + ">";
00390             emptyLines = QString::null;
00391             // Append a CR, but in a temp string -> if no other real line,
00392             // then those will be dropped
00393             emptyLines += "\n";
00394         }
00395     }
00396     str += "\n</" + html_table_tag + ">\n<br>\n";
00397 }
00398 
00399 void HTMLExport::createSheetSeparator( QString &str )
00400 {
00401   str += ("<p align=\"" + html_center + "\"><a href=\"#__top\">" + i18n("Top") + "</a></p>\n" );
00402   str += "<hr width=\"80%\">\n";
00403 }
00404 
00405 void HTMLExport::writeTOC( const QStringList &sheets, const QString &base, QString &str )
00406 {
00407   // don't create TOC for 1 sheet
00408   if( sheets.count() == 1 )
00409     return;
00410 
00411   str += "<p align=\"" + html_center + "\">\n";
00412 
00413   for( uint i = 0 ; i < sheets.count() ; ++i )
00414   {
00415     str += "<a href=\"";
00416 
00417     if( m_dialog->separateFiles() )
00418     {
00419       str += fileName( base, sheets[i], sheets.count() > 1  );
00420     }
00421     else
00422     {
00423       str += "#" + sheets[i].lower().stripWhiteSpace();
00424     }
00425 
00426     str += "\">" + sheets[i] + "</a>\n";
00427     if( i != sheets.count() -1 )
00428       str += " - ";
00429   }
00430 
00431   str += "</p><hr width=\"80%\">\n";
00432 }
00433 
00434 QString HTMLExport::fileName( const QString &base, const QString &sheetName, bool multipleFiles )
00435 {
00436      QString fileName = base;
00437      if( m_dialog->separateFiles() && multipleFiles )
00438      {
00439          fileName += "-" + sheetName;
00440      }
00441      fileName += ".html";
00442 
00443      return fileName;
00444 }
00445 
00446 void HTMLExport::detectFilledCells( Sheet *sheet, int &rows, int &columns )
00447 {
00448   int iMaxColumn = sheet->maxColumn();
00449   int iMaxRow = sheet->maxRow();
00450   rows = 0;
00451   columns = 0;
00452 
00453   for ( int currentrow = 1 ; currentrow <= iMaxRow ; ++currentrow)
00454   {
00455     Cell * cell = 0L;
00456     int iUsedColumn=0;
00457     for ( int currentcolumn = 1 ; currentcolumn <= iMaxColumn ; currentcolumn++ )
00458     {
00459       cell = sheet->cellAt( currentcolumn, currentrow, false );
00460       QString text;
00461       if ( !cell->isDefault() && !cell->isEmpty() )
00462       {
00463         iUsedColumn = currentcolumn;
00464       }
00465     }
00466     if (cell)
00467       iUsedColumn += cell->extraXCells();
00468     if (iUsedColumn > columns)
00469       columns = iUsedColumn;
00470     if ( iUsedColumn > 0 )
00471       rows = currentrow;
00472   }
00473 }
00474 
00475 #include <htmlexport.moc>
KDE Home | KDE Accessibility Home | Description of Access Keys