Warning, file /office/calligra/filters/sheets/html/htmlexport.cc was not indexed or was modified since last indexation (in which case cross-reference links may be missing, inaccurate or erroneous).
0001 /* This file is part of the KDE project 0002 SPDX-FileCopyrightText: 2001 Eva Brucherseifer <eva@kde.org> 0003 SPDX-FileCopyrightText: 2005 Bram Schoenmakers <bramschoenmakers@kde.nl> 0004 based on kspread csv export filter by David Faure 0005 0006 SPDX-License-Identifier: LGPL-2.0-or-later 0007 */ 0008 0009 #include "htmlexport.h" 0010 #include "exportdialog.h" 0011 0012 // #include <QFile> 0013 #include <QTextCodec> 0014 // #include <QTextStream> 0015 // #include <QByteArray> 0016 // #include <QUrl> 0017 0018 #include <kpluginfactory.h> 0019 #include <KoFilterChain.h> 0020 #include <KoFilterManager.h> 0021 #include <KoDocumentInfo.h> 0022 #include <CalligraVersionWrapper.h> 0023 0024 #include <sheets/engine/CellBaseStorage.h> 0025 #include <sheets/engine/Util.h> 0026 #include <sheets/core/Cell.h> 0027 #include <sheets/core/DocBase.h> 0028 #include <sheets/core/Map.h> 0029 #include <sheets/core/Sheet.h> 0030 #include <sheets/core/Style.h> 0031 0032 using namespace Calligra::Sheets; 0033 0034 Q_LOGGING_CATEGORY(lcHtml, "calligra.filter.html") 0035 0036 K_PLUGIN_FACTORY_WITH_JSON(HTMLExportFactory, "calligra_filter_sheets2html.json", 0037 registerPlugin<HTMLExport>();) 0038 0039 const QString html_table_tag = "table"; 0040 const QString html_table_options = QString(" border=\"%1\" cellspacing=\"%2\""); 0041 const QString html_row_tag = "tr"; 0042 const QString html_row_options = ""; 0043 const QString html_cell_tag = "td"; 0044 const QString html_cell_options = ""; 0045 const QString html_bold = "b"; 0046 const QString html_italic = "i"; 0047 const QString html_underline = "u"; 0048 const QString html_right = "right"; 0049 const QString html_left = "left"; 0050 const QString html_center = "center"; 0051 const QString html_top = "top"; 0052 const QString html_bottom = "bottom"; 0053 const QString html_middle = "middle"; 0054 const QString html_h1 = "h1"; 0055 0056 HTMLExport::HTMLExport(QObject* parent, const QVariantList&) : 0057 KoFilter(parent), m_dialog(new ExportDialog()) 0058 { 0059 } 0060 0061 HTMLExport::~HTMLExport() 0062 { 0063 delete m_dialog; 0064 } 0065 0066 // HTML entities, AFAIK we don't need to escape " to " (dnaber): 0067 const QString strAmp("&"); 0068 const QString nbsp(" "); 0069 const QString strLt("<"); 0070 const QString strGt(">"); 0071 0072 // The reason why we use the KoDocument* approach and not the QDomDocument 0073 // approach is because we don't want to export formulas but values ! 0074 KoFilter::ConversionStatus HTMLExport::convert(const QByteArray& from, const QByteArray& to) 0075 { 0076 if (to != "text/html" || from != "application/x-kspread") { 0077 qWarning(lcHtml) << "Invalid mimetypes " << to << " " << from; 0078 return KoFilter::NotImplemented; 0079 } 0080 0081 KoDocument* document = m_chain->inputDocument(); 0082 0083 if (!document) 0084 return KoFilter::StupidError; 0085 0086 if (!::qobject_cast<const Calligra::Sheets::DocBase *>(document)) { // it's safer that way :) 0087 qWarning(lcHtml) << "document isn't a Calligra::Sheets::DocBase but a " << document->metaObject()->className(); 0088 return KoFilter::NotImplemented; 0089 } 0090 0091 const DocBase * ksdoc = dynamic_cast<const DocBase *>(document); 0092 0093 if (ksdoc->mimeType() != "application/x-kspread") { 0094 qWarning(lcHtml) << "Invalid document mimetype " << ksdoc->mimeType(); 0095 return KoFilter::NotImplemented; 0096 } 0097 0098 QString filenameBase = m_chain->outputFile(); 0099 filenameBase = filenameBase.left(filenameBase.lastIndexOf('.')); 0100 0101 QStringList sheets; 0102 for(SheetBase* bsheet : ksdoc->map()->sheetList()) { 0103 Sheet *sheet = dynamic_cast<Sheet *>(bsheet); 0104 int rows = 0; 0105 int columns = 0; 0106 detectFilledCells(sheet, rows, columns); 0107 m_rowmap[ sheet->sheetName()] = rows; 0108 m_columnmap[ sheet->sheetName()] = columns; 0109 0110 if (rows > 0 && columns > 0) { 0111 sheets.append(sheet->sheetName()); 0112 } 0113 } 0114 m_dialog->setSheets(sheets); 0115 if (!m_chain->manager()->getBatchMode() ) { 0116 if (m_dialog->exec() == QDialog::Rejected) { 0117 return KoFilter::UserCancelled; 0118 } 0119 } 0120 0121 sheets = m_dialog->sheets(); 0122 QString str; 0123 for (int i = 0; i < sheets.count() ; ++i) { 0124 SheetBase *bsheet = ksdoc->map()->findSheet(sheets[i]); 0125 Sheet *sheet = dynamic_cast<Sheet *>(bsheet); 0126 if (!sheet) 0127 continue; 0128 0129 QString file = fileName(filenameBase, sheet->sheetName(), sheets.count() > 1); 0130 0131 if (m_dialog->separateFiles() || sheets[i] == sheets.first()) { 0132 str.clear(); 0133 openPage(sheet, document, str); 0134 writeTOC(sheets, filenameBase, str); 0135 } 0136 0137 convertSheet(sheet, str, m_rowmap[ sheet->sheetName()], m_columnmap[ sheet->sheetName()]); 0138 0139 if (m_dialog->separateFiles() || sheets[i] == sheets.last()) { 0140 closePage(str); 0141 QFile out(file); 0142 if (!out.open(QIODevice::WriteOnly)) { 0143 qWarning(lcHtml) << "Unable to open output file!" << endl; 0144 out.close(); 0145 return KoFilter::FileNotFound; 0146 } 0147 QTextStream streamOut(&out); 0148 streamOut.setCodec(m_dialog->encoding()); 0149 streamOut << str << endl; 0150 out.close(); 0151 } 0152 0153 if (!m_dialog->separateFiles()) { 0154 createSheetSeparator(str); 0155 } 0156 0157 } 0158 0159 emit sigProgress(100); 0160 return KoFilter::OK; 0161 } 0162 0163 void HTMLExport::openPage(Sheet *sheet, KoDocument *document, QString &str) 0164 { 0165 QString title; 0166 KoDocumentInfo *info = document->documentInfo(); 0167 if (info && !info->aboutInfo("title").isEmpty()) 0168 title = info->aboutInfo("title") + " - "; 0169 title += sheet->sheetName(); 0170 0171 // header 0172 str = "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\" " 0173 " \"http://www.w3.org/TR/html4/loose.dtd\"> \n" 0174 "<html>\n" 0175 "<head>\n" 0176 "<meta http-equiv=\"Content-Type\" " + 0177 QString("content=\"text/html; charset=%1\">\n").arg(QString(m_dialog->encoding()->name())) + 0178 "<meta name=\"Generator\" " 0179 "content=\"KSpread HTML Export Filter Version = " + 0180 CalligraVersionWrapper::versionString() + 0181 "\">\n"; 0182 0183 // Insert stylesheet 0184 if (!m_dialog->customStyleURL().isEmpty()) { 0185 str += "<link ref=\"stylesheet\" type=\"text/css\" href=\"" + 0186 m_dialog->customStyleURL().url() + 0187 "\" title=\"Style\" >\n"; 0188 } 0189 0190 str += "<title>" + title + "</title>\n" 0191 "</head>\n" + 0192 QString("<body bgcolor=\"#FFFFFF\" dir=\"%1\">\n").arg( 0193 (sheet->layoutDirection() == Qt::RightToLeft) ? "rtl" : "ltr") + 0194 0195 "<a name=\"__top\">\n"; 0196 } 0197 0198 void HTMLExport::closePage(QString &str) 0199 { 0200 str += "<p align=\"" + html_center + "\"><a href=\"#__top\">" + i18n("Top") + "</a></p>\n" 0201 "</body>\n" 0202 "</html>\n\n"; 0203 } 0204 0205 void HTMLExport::convertSheet(Sheet *sheet, QString &str, int iMaxUsedRow, int iMaxUsedColumn) 0206 { 0207 QString emptyLines; 0208 0209 // Either we get hold of KSpreadTable::m_dctCells and apply the old method below (for sorting) 0210 // or, cleaner and already sorted, we use KSpreadTable's API (slower probably, though) 0211 int iMaxRow = sheet->cellStorage()->rows(); 0212 0213 if (!m_dialog->separateFiles()) 0214 str += "<a name=\"" + sheet->sheetName().toLower().trimmed() + "\">\n"; 0215 0216 str += ("<h1>" + sheet->sheetName() + "</h1><br>\n"); 0217 0218 // this is just a bad approximation which fails for documents with less than 50 rows, but 0219 // we don't need any progress stuff there anyway :) (Werner) 0220 int value = 0; 0221 int step = iMaxRow > 50 ? iMaxRow / 50 : 1; 0222 int i = 1; 0223 0224 str += '<' + html_table_tag + html_table_options.arg(m_dialog->useBorders() ? "1" : "0").arg(m_dialog->pixelsBetweenCells()) + 0225 QString("dir=\"%1\">\n").arg((sheet->layoutDirection() == Qt::RightToLeft) ? "rtl" : "ltr"); 0226 0227 unsigned int nonempty_cells_prev = 0; 0228 0229 for (int currentrow = 1 ; currentrow <= iMaxUsedRow ; ++currentrow, ++i) { 0230 if (i > step) { 0231 value += 2; 0232 emit sigProgress(value); 0233 i = 0; 0234 } 0235 0236 QString line; 0237 unsigned int nonempty_cells = 0; 0238 0239 for (int currentcolumn = 1 ; currentcolumn <= iMaxUsedColumn ; currentcolumn++) { 0240 Cell cell(sheet, currentcolumn, currentrow); 0241 const Style style = cell.effectiveStyle(); 0242 if (cell.needsPrinting()) 0243 nonempty_cells++; 0244 QString text; 0245 // FIXME: some formatting seems to be missing with cell.userInput(), e.g. 0246 // "208.00" in KSpread will be "208" in HTML (not always?!) 0247 bool link = false; 0248 0249 if (!cell.link().isEmpty()) { 0250 if (Util::localReferenceAnchor(cell.link())) { 0251 text = cell.userInput(); 0252 } else { 0253 text = " <A href=\"" + cell.link() + "\">" + cell.userInput() + "</A>"; 0254 link = true; 0255 } 0256 } else 0257 text = cell.displayText(); 0258 #if 0 0259 switch (cell.content()) { 0260 case Cell::Text: 0261 text = cell.userInput(); 0262 break; 0263 case Cell::RichText: 0264 case Cell::VisualFormula: 0265 text = cell.userInput(); // untested 0266 break; 0267 case Cell::Formula: 0268 cell.calc(true); // Incredible, cells are not calculated if the document was just opened 0269 text = cell.valueString(); 0270 break; 0271 } 0272 text = cell.prefix(currentrow, currentcolumn) + ' ' + text + ' ' 0273 + cell.postfix(currentrow, currentcolumn); 0274 #endif 0275 line += " <" + html_cell_tag + html_cell_options; 0276 if (text.isRightToLeft() != (sheet->layoutDirection() == Qt::RightToLeft)) 0277 line += QString(" dir=\"%1\" ").arg(text.isRightToLeft() ? "rtl" : "ltr"); 0278 const QColor bgcolor = style.backgroundColor(); 0279 if (bgcolor.isValid() && bgcolor.name() != "#ffffff") // change color only for non-white cells 0280 line += " bgcolor=\"" + bgcolor.name() + "\""; 0281 0282 switch ((Style::HAlign)cell.effectiveAlignX()) { 0283 case Style::Left: 0284 line += " align=\"" + html_left + "\""; 0285 break; 0286 case Style::Right: 0287 line += " align=\"" + html_right + "\""; 0288 break; 0289 case Style::Center: 0290 line += " align=\"" + html_center + "\""; 0291 break; 0292 case Style::HAlignUndefined: 0293 case Style::Justified: 0294 break; 0295 } 0296 switch ((Style::VAlign) style.valign()) { 0297 case Style::Top: 0298 line += " valign=\"" + html_top + "\""; 0299 break; 0300 case Style::Middle: 0301 line += " valign=\"" + html_middle + "\""; 0302 break; 0303 case Style::Bottom: 0304 line += " valign=\"" + html_bottom + "\""; 0305 break; 0306 case Style::VAlignUndefined: 0307 case Style::VJustified: 0308 case Style::VDistributed: 0309 break; 0310 } 0311 line += " width=\"" + QString::number(cell.width()) + "\""; 0312 line += " height=\"" + QString::number(cell.height()) + "\""; 0313 0314 if (cell.mergedXCells() > 0) { 0315 QString tmp; 0316 int extra_cells = cell.mergedXCells(); 0317 line += " colspan=\"" + tmp.setNum(extra_cells + 1) + "\""; 0318 currentcolumn += extra_cells; 0319 } 0320 text = text.trimmed(); 0321 if (!text.isEmpty() && text.at(0) == '!') { 0322 // this is supposed to be markup, just remove the '!': 0323 text = text.right(text.length() - 1); 0324 } else if (!link) { 0325 // Escape HTML characters. 0326 text.replace('&' , strAmp) 0327 .replace('<' , strLt) 0328 .replace('>' , strGt) 0329 .replace(' ' , nbsp); 0330 } 0331 line += ">\n"; 0332 0333 if (style.bold()) { 0334 text.insert(0, '<' + html_bold + '>'); 0335 text.append("</" + html_bold + '>'); 0336 } 0337 if (style.italic()) { 0338 text.insert(0, '<' + html_italic + '>'); 0339 text.append("</" + html_italic + '>'); 0340 } 0341 if (style.underline()) { 0342 text.insert(0, '<' + html_underline + '>'); 0343 text.append("</" + html_underline + '>'); 0344 } 0345 QColor textColor = style.fontColor(); 0346 if (textColor.isValid() && textColor.name() != "#000000") { // change color only for non-default text 0347 text.insert(0, "<font color=\"" + textColor.name() + "\">"); 0348 text.append("</font>"); 0349 } 0350 line += ' ' + text + 0351 "\n </" + html_cell_tag + ">\n"; 0352 } 0353 0354 if (nonempty_cells == 0 && nonempty_cells_prev == 0) { 0355 nonempty_cells_prev = nonempty_cells; 0356 // skip line if there's more than one empty line 0357 continue; 0358 } else { 0359 nonempty_cells_prev = nonempty_cells; 0360 str += emptyLines + 0361 '<' + html_row_tag + html_row_options + ">\n" + 0362 line + 0363 "</" + html_row_tag + '>'; 0364 emptyLines.clear(); 0365 // Append a CR, but in a temp string -> if no other real line, 0366 // then those will be dropped 0367 emptyLines += '\n'; 0368 } 0369 } 0370 str += "\n</" + html_table_tag + ">\n<br>\n"; 0371 } 0372 0373 void HTMLExport::createSheetSeparator(QString &str) 0374 { 0375 str += "<p align=\"" + html_center + "\"><a href=\"#__top\">" + i18n("Top") + "</a></p>\n" 0376 "<hr width=\"80%\">\n"; 0377 } 0378 0379 void HTMLExport::writeTOC(const QStringList &sheets, const QString &base, QString &str) 0380 { 0381 // don't create TOC for 1 sheet 0382 if (sheets.count() == 1) 0383 return; 0384 0385 str += "<p align=\"" + html_center + "\">\n"; 0386 0387 for (int i = 0 ; i < sheets.count() ; ++i) { 0388 str += "<a href=\""; 0389 0390 if (m_dialog->separateFiles()) { 0391 str += fileName(base, sheets[i], sheets.count() > 1); 0392 } else { 0393 str += '#' + sheets[i].toLower().trimmed(); 0394 } 0395 0396 str += "\">" + sheets[i] + "</a>\n"; 0397 if (i != sheets.count() - 1) 0398 str += " - "; 0399 } 0400 0401 str += "</p><hr width=\"80%\">\n"; 0402 } 0403 0404 QString HTMLExport::fileName(const QString &base, const QString &sheetName, bool multipleFiles) 0405 { 0406 QString fileName = base; 0407 if (m_dialog->separateFiles() && multipleFiles) { 0408 fileName += '-' + sheetName; 0409 } 0410 fileName += ".html"; 0411 0412 return fileName; 0413 } 0414 0415 void HTMLExport::detectFilledCells(Sheet *sheet, int &rows, int &columns) 0416 { 0417 int iMaxColumn = sheet->cellStorage()->columns(); 0418 int iMaxRow = sheet->cellStorage()->rows(); 0419 0420 rows = 0; 0421 columns = 0; 0422 0423 for (int currentrow = 1 ; currentrow <= iMaxRow ; ++currentrow) { 0424 Cell cell; 0425 int iUsedColumn = 0; 0426 for (int currentcolumn = 1 ; currentcolumn <= iMaxColumn ; currentcolumn++) { 0427 cell = Cell(sheet, currentcolumn, currentrow); 0428 if (!cell.isDefault() && !cell.isEmpty()) { 0429 iUsedColumn = currentcolumn; 0430 } 0431 } 0432 if (!cell.isNull()) 0433 iUsedColumn += cell.mergedXCells(); 0434 if (iUsedColumn > columns) 0435 columns = iUsedColumn; 0436 if (iUsedColumn > 0) 0437 rows = currentrow; 0438 } 0439 } 0440 0441 #include <htmlexport.moc>