filters

kspread/html/htmlexport.cpp
1/* This file is part of the KDE project
2 Copyright (C) 2001 Eva Brucherseifer <eva@kde.org>
3 Copyright (C) 2005 Bram Schoenmakers <bramschoenmakers@kde.nl>
4 based on kspread csv export filter by David Faure
5
6 This library is free software; you can redistribute it and/or
7 modify it under the terms of the GNU Library General Public
8 License as published by the Free Software Foundation; either
9 version 2 of the License, or (at your option) any later version.
10
11 This library is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 Library General Public License for more details.
15
16 You should have received a copy of the GNU Library General Public License
17 along with this library; see the file COPYING.LIB. If not, write to
18 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
19 * Boston, MA 02110-1301, USA.
20*/
21
22#include <htmlexport.h>
23#include <exportdialog.h>
24
25#include <tqfile.h>
26#include <tqtextcodec.h>
27
28#include <kdebug.h>
29#include <kgenericfactory.h>
30#include <KoFilterChain.h>
31#include <KoDocumentInfo.h>
32#include <kofficeversion.h>
33
34#include <kspread_map.h>
35#include <kspread_sheet.h>
36#include <kspread_doc.h>
37#include <kspread_util.h>
38
39using namespace KSpread;
40
41typedef KGenericFactory<HTMLExport, KoFilter> HTMLExportFactory;
42K_EXPORT_COMPONENT_FACTORY( libkspreadhtmlexport, HTMLExportFactory( "kofficefilters" ) )
43
44const TQString html_table_tag = "table";
45const TQString html_table_options = TQString(" border=\"%1\" cellspacing=\"%2\"");
46const TQString html_row_tag = "tr";
47const TQString html_row_options = "";
48const TQString html_cell_tag = "td";
49const TQString html_cell_options = "";
50const TQString html_bold = "b";
51const TQString html_italic = "i";
52const TQString html_underline = "u";
53const TQString html_right= "right";
54const TQString html_left= "left";
55const TQString html_center= "center";
56const TQString html_top="top";
57const TQString html_bottom="bottom";
58const TQString html_middle="middle";
59const TQString html_h1="h1";
60
61HTMLExport::HTMLExport(KoFilter *, const char *, const TQStringList&) :
62 KoFilter(), m_dialog( new ExportDialog() )
63{
64}
65
66HTMLExport::~HTMLExport()
67{
68 delete m_dialog;
69}
70
71// HTML enitities, AFAIK we don't need to escape " to &quot; (dnaber):
72const TQString strAmp ("&amp;");
73const TQString nbsp ("&nbsp;");
74const TQString strLt ("&lt;");
75const TQString strGt ("&gt;");
76
77// The reason why we use the KoDocument* approach and not the TQDomDocument
78// approach is because we don't want to export formulas but values !
79KoFilter::ConversionStatus HTMLExport::convert( const TQCString& from, const TQCString& to )
80{
81 if(to!="text/html" || from!="application/x-kspread")
82 {
83 kdWarning(30501) << "Invalid mimetypes " << to << " " << from << endl;
84 return KoFilter::NotImplemented;
85 }
86
87 KoDocument* document = m_chain->inputDocument();
88
89 if ( !document )
90 return KoFilter::StupidError;
91
92 if( !::tqt_cast<const KSpread::Doc *>( document ) ) // it's safer that way :)
93 {
94 kdWarning(30501) << "document isn't a KSpread::Doc but a " << document->className() << endl;
95 return KoFilter::NotImplemented;
96 }
97
98 const Doc * ksdoc=static_cast<const Doc *>(document);
99
100 if( ksdoc->mimeType() != "application/x-kspread" )
101 {
102 kdWarning(30501) << "Invalid document mimetype " << ksdoc->mimeType() << endl;
103 return KoFilter::NotImplemented;
104 }
105
106 Sheet *sheet = ksdoc->map()->firstSheet();
107 TQString filenameBase = m_chain->outputFile();
108 filenameBase = filenameBase.left( filenameBase.findRev( '.' ) );
109
110 TQStringList sheets;
111 while( sheet != 0 )
112 {
113 int rows = 0;
114 int columns = 0;
115 detectFilledCells( sheet, rows, columns );
116 m_rowmap[ sheet->sheetName() ] = rows;
117 m_columnmap[ sheet->sheetName() ] = columns;
118
119 if( rows > 0 && columns > 0 )
120 {
121 sheets.append( sheet->sheetName() );
122 }
123 sheet = ksdoc->map()->nextSheet();
124 }
125 m_dialog->setSheets( sheets );
126
127 if( m_dialog->exec() == TQDialog::Rejected )
128 return KoFilter::UserCancelled;
129
130 sheets = m_dialog->sheets();
131 TQString str;
132 for( uint i = 0; i < sheets.count() ; ++i )
133 {
134 sheet = ksdoc->map()->findSheet( sheets[i] );
135
136 TQString file = fileName( filenameBase, sheet->sheetName(), sheets.count() > 1 );
137
138 if( m_dialog->separateFiles() || sheets[i] == sheets.first() )
139 {
140 str = TQString();
141 openPage( sheet, document, str );
142 writeTOC( sheets, filenameBase, str );
143 }
144
145 convertSheet( sheet, str, m_rowmap[ sheet->sheetName() ], m_columnmap[ sheet->sheetName() ] );
146
147 if( m_dialog->separateFiles() || sheets[i] == sheets.last() )
148 {
149 closePage( str );
150 TQFile out(file);
151 if(!out.open(IO_WriteOnly)) {
152 kdError(30501) << "Unable to open output file!" << endl;
153 out.close();
154 return KoFilter::FileNotFound;
155 }
156 TQTextStream streamOut(&out);
157 streamOut.setCodec( m_dialog->encoding() );
158 streamOut << str << endl;
159 out.close();
160 }
161
162 if( !m_dialog->separateFiles() )
163 {
164 createSheetSeparator( str );
165 }
166
167 }
168
169 emit sigProgress(100);
170 return KoFilter::OK;
171}
172
173void HTMLExport::openPage( Sheet *sheet, KoDocument *document, TQString &str )
174{
175 TQString title;
176 KoDocumentInfo *info = document->documentInfo();
177 KoDocumentInfoAbout *aboutPage = static_cast<KoDocumentInfoAbout *>(info->page( "about" ));
178 if ( aboutPage && !aboutPage->title().isEmpty() )
179 title = aboutPage->title() + " - ";
180
181 title += sheet->sheetName();
182
183 // header
184 str = "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\" ";
185 str += " \"http://www.w3.org/TR/html4/loose.dtd\"> \n";
186 str += "<html>\n";
187 str += "<head>\n";
188 str += "<meta http-equiv=\"Content-Type\" ";
189 str += TQString("content=\"text/html; charset=%1\">\n").arg( m_dialog->encoding()->mimeName() );
190 str += "<meta name=\"Generator\" ";
191 str += "content=\"KSpread HTML Export Filter Version = ";
192 str += KOFFICE_VERSION_STRING;
193 str += "\">\n";
194
195 // Insert stylesheet
196 if( !m_dialog->customStyleURL().isEmpty() )
197 {
198 str += "<link ref=\"stylesheet\" type=\"text/css\" href=\"";
199 str += m_dialog->customStyleURL();
200 str += "\" title=\"Style\" >\n";
201 }
202
203 str += "<title>" + title + "</title>\n";
204 str += "</head>\n";
205 str += TQString("<body bgcolor=\"#FFFFFF\" dir=\"%1\">\n").arg(
206 sheet->isRightToLeft()?"rtl":"ltr");
207
208 str += "<a name=\"__top\">\n";
209}
210
211void HTMLExport::closePage( TQString &str )
212{
213 str += "<p align=\"" + html_center + "\"><a href=\"#__top\">" + i18n("Top") + "</a></p>\n";
214 str += "</body>\n";
215 str += "</html>\n\n";
216}
217
218void HTMLExport::convertSheet( Sheet *sheet, TQString &str, int iMaxUsedRow, int iMaxUsedColumn )
219{
220 TQString emptyLines;
221
222 // Either we get hold of KSpreadTable::m_dctCells and apply the old method below (for sorting)
223 // or, cleaner and already sorted, we use KSpreadTable's API (slower probably, though)
224 int iMaxRow = sheet->maxRow();
225
226 if( !m_dialog->separateFiles() )
227 str += "<a name=\"" + sheet->sheetName().lower().stripWhiteSpace() + "\">\n";
228
229 str += ("<h1>" + sheet->sheetName() + "</h1><br>\n");
230
231 // this is just a bad approximation which fails for documents with less than 50 rows, but
232 // we don't need any progress stuff there anyway :) (Werner)
233 int value=0;
234 int step=iMaxRow > 50 ? iMaxRow/50 : 1;
235 int i=1;
236
237 str += "<" + html_table_tag + html_table_options.arg( m_dialog->useBorders() ? "1" : "0" ).arg( m_dialog->pixelsBetweenCells() ) +
238 TQString("dir=\"%1\">\n").arg(sheet->isRightToLeft()?"rtl":"ltr");
239
240 unsigned int nonempty_cells_prev=0;
241
242 for ( int currentrow = 1 ; currentrow <= iMaxUsedRow ; ++currentrow, ++i )
243 {
244 if(i>step) {
245 value+=2;
246 emit sigProgress(value);
247 i=0;
248 }
249
250 TQString separators;
251 TQString line;
252 unsigned int nonempty_cells=0;
253 unsigned int colspan_cells=0;
254
255 for ( int currentcolumn = 1 ; currentcolumn <= iMaxUsedColumn ; currentcolumn++ )
256 {
257 Cell * cell = sheet->cellAt( currentcolumn, currentrow, false );
258 colspan_cells=cell->extraXCells();
259 if (cell->needsPrinting())
260 nonempty_cells++;
261 TQString text;
262 TQColor bgcolor = cell->bgColor(currentcolumn,currentrow);
263 // FIXME: some formatting seems to be missing with cell->text(), e.g.
264 // "208.00" in KSpread will be "208" in HTML (not always?!)
265 bool link = false;
266
267 if ( !cell->link().isEmpty() )
268 {
269 if ( localReferenceAnchor(cell->link()) )
270 {
271 text = cell->text();
272 }
273 else
274 {
275 text = " <A href=\"" + cell->link() + "\">" + cell->text() + "</A>";
276 link = true;
277 }
278 }
279 else
280 text=cell->strOutText();
281#if 0
282 switch( cell->content() ) {
283 case Cell::Text:
284 text = cell->text();
285 break;
286 case Cell::RichText:
287 case Cell::VisualFormula:
288 text = cell->text(); // untested
289 break;
290 case Cell::Formula:
291 cell->calc( true ); // Incredible, cells are not calculated if the document was just opened
292 text = cell->valueString();
293 break;
294 }
295 text = cell->prefix(currentrow, currentcolumn) + " " + text + " "
296 + cell->postfix(currentrow, currentcolumn);
297#endif
298 line += " <" + html_cell_tag + html_cell_options;
299 if (text.isRightToLeft() != sheet->isRightToLeft())
300 line += TQString(" dir=\"%1\" ").arg(text.isRightToLeft()?"rtl":"ltr");
301 if (bgcolor.isValid() && bgcolor.name()!="#ffffff") // change color only for non-white cells
302 line += " bgcolor=\"" + bgcolor.name() + "\"";
303
304 switch((Format::Align)cell->defineAlignX())
305 {
306 case Format::Left:
307 line+=" align=\"" + html_left +"\"";
308 break;
309 case Format::Right:
310 line+=" align=\"" + html_right +"\"";
311 break;
312 case Format::Center:
313 line+=" align=\"" + html_center +"\"";
314 break;
315 case Format::Undefined:
316 break;
317 }
318 switch((Format::AlignY)cell-> format()->alignY(currentrow, currentcolumn))
319 {
320 case Format::Top:
321 line+=" valign=\"" + html_top +"\"";
322 break;
323 case Format::Middle:
324 line+=" valign=\"" + html_middle +"\"";
325 break;
326 case Format::Bottom:
327 line+=" valign=\"" + html_bottom +"\"";
328 break;
329 case Format::UndefinedY:
330 break;
331 }
332 line+=" width=\""+TQString::number(cell->width())+"\"";
333 line+=" height=\""+TQString::number(cell->height())+"\"";
334
335 if (cell->extraXCells()>0)
336 {
337 TQString tmp;
338 int extra_cells=cell->extraXCells();
339 line += " colspan=\"" + tmp.setNum(extra_cells+1) + "\"";
340 currentcolumn += extra_cells;
341 }
342 text = text.stripWhiteSpace();
343 if( text.at(0) == '!' ) {
344 // this is supposed to be markup, just remove the '!':
345 text = text.right(text.length()-1);
346 } else if ( !link ) {
347 // Escape HTML characters.
348 text.replace ('&' , strAmp)
349 .replace ('<' , strLt)
350 .replace ('>' , strGt)
351 .replace (' ' , nbsp);
352 }
353 line += ">\n";
354
355 if (cell->format()->textFontBold(currentcolumn,currentrow))
356 {
357 text.insert(0, "<" + html_bold + ">");
358 text.append("</" + html_bold + ">");
359 }
360 if (cell->format()->textFontItalic(currentcolumn,currentrow))
361 {
362 text.insert(0, "<" + html_italic + ">");
363 text.append("</" + html_italic + ">");
364 }
365 if (cell->format()->textFontUnderline(currentcolumn,currentrow))
366 {
367 text.insert(0, "<" + html_underline + ">");
368 text.append("</" + html_underline + ">");
369 }
370 TQColor textColor = cell->format()->textColor(currentcolumn,currentrow);
371 if (textColor.isValid() && textColor.name()!="#000000") // change color only for non-default text
372 {
373 text.insert(0, "<font color=\"" + textColor.name() + "\">");
374 text.append("</font>");
375 }
376 line += " " + text;
377 line += "\n </" + html_cell_tag + ">\n";
378 }
379
380 if (nonempty_cells == 0 && nonempty_cells_prev == 0) {
381 nonempty_cells_prev = nonempty_cells;
382 // skip line if there's more than one empty line
383 continue;
384 } else {
385 nonempty_cells_prev = nonempty_cells;
386 str += emptyLines;
387 str += "<" + html_row_tag + html_row_options + ">\n";
388 str += line;
389 str += "</" + html_row_tag + ">";
390 emptyLines = TQString();
391 // Append a CR, but in a temp string -> if no other real line,
392 // then those will be dropped
393 emptyLines += "\n";
394 }
395 }
396 str += "\n</" + html_table_tag + ">\n<br>\n";
397}
398
399void HTMLExport::createSheetSeparator( TQString &str )
400{
401 str += ("<p align=\"" + html_center + "\"><a href=\"#__top\">" + i18n("Top") + "</a></p>\n" );
402 str += "<hr width=\"80%\">\n";
403}
404
405void HTMLExport::writeTOC( const TQStringList &sheets, const TQString &base, TQString &str )
406{
407 // don't create TOC for 1 sheet
408 if( sheets.count() == 1 )
409 return;
410
411 str += "<p align=\"" + html_center + "\">\n";
412
413 for( uint i = 0 ; i < sheets.count() ; ++i )
414 {
415 str += "<a href=\"";
416
417 if( m_dialog->separateFiles() )
418 {
419 str += fileName( base, sheets[i], sheets.count() > 1 );
420 }
421 else
422 {
423 str += "#" + sheets[i].lower().stripWhiteSpace();
424 }
425
426 str += "\">" + sheets[i] + "</a>\n";
427 if( i != sheets.count() -1 )
428 str += " - ";
429 }
430
431 str += "</p><hr width=\"80%\">\n";
432}
433
434TQString HTMLExport::fileName( const TQString &base, const TQString &sheetName, bool multipleFiles )
435{
436 TQString fileName = base;
437 if( m_dialog->separateFiles() && multipleFiles )
438 {
439 fileName += "-" + sheetName;
440 }
441 fileName += ".html";
442
443 return fileName;
444}
445
446void HTMLExport::detectFilledCells( Sheet *sheet, int &rows, int &columns )
447{
448 int iMaxColumn = sheet->maxColumn();
449 int iMaxRow = sheet->maxRow();
450 rows = 0;
451 columns = 0;
452
453 for ( int currentrow = 1 ; currentrow <= iMaxRow ; ++currentrow)
454 {
455 Cell * cell = 0L;
456 int iUsedColumn=0;
457 for ( int currentcolumn = 1 ; currentcolumn <= iMaxColumn ; currentcolumn++ )
458 {
459 cell = sheet->cellAt( currentcolumn, currentrow, false );
460 TQString text;
461 if ( !cell->isDefault() && !cell->isEmpty() )
462 {
463 iUsedColumn = currentcolumn;
464 }
465 }
466 if (cell)
467 iUsedColumn += cell->extraXCells();
468 if (iUsedColumn > columns)
469 columns = iUsedColumn;
470 if ( iUsedColumn > 0 )
471 rows = currentrow;
472 }
473}
474
475#include <htmlexport.moc>