1 /* This file is part of the KDE project
2    Copyright (C) 2003-2006 Ariya Hidayat <ariya@kde.org>
3    Copyright (C) 2006 Marijn Kruisselbrink <mkruisselbrink@kde.org>
4    Copyright (C) 2009-2010 Nokia Corporation and/or its subsidiary(-ies).
5    Contact: Manikandaprasad Chandrasekar <manikandaprasad.chandrasekar@nokia.com>
6    Copyright (c) 2010 Carlos Licea <carlos@kdab.com>
7 
8    This library is free software; you can redistribute it and/or
9    modify it under the terms of the GNU Library General Public
10    License as published by the Free Software Foundation; either
11    version 2 of the License, or (at your option) any later version.
12 
13    This library is distributed in the hope that it will be useful,
14    but WITHOUT ANY WARRANTY; without even the implied warranty of
15    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
16    Library General Public License for more details.
17 
18    You should have received a copy of the GNU Library General Public License
19    along with this library; see the file COPYING.LIB.  If not, write to
20    the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
21  * Boston, MA 02110-1301, USA.
22 */
23 
24 #include "ExcelImport.h"
25 
26 #include <QString>
27 #include <QDate>
28 #include <QBuffer>
29 #include <QFontMetricsF>
30 #include <QPair>
31 #include <QTextCursor>
32 
33 #include <KoFilterChain.h>
34 #include <kpluginfactory.h>
35 #include <klocale.h>
36 
37 #include <KoXmlWriter.h>
38 #include <KoGenStyles.h>
39 #include <KoGenStyle.h>
40 #include <KoXmlNS.h>
41 #include <KoShapeLoadingContext.h>
42 #include <KoShapeRegistry.h>
43 #include <KoOdfStylesReader.h>
44 #include <KoOdfLoadingContext.h>
45 #include <KoShape.h>
46 #include <KoDocumentInfo.h>
47 #include <KoTextDocument.h>
48 
49 #include <DocBase.h>
50 #include <sheets/Sheet.h>
51 #include <sheets/Condition.h>
52 #include <sheets/odf/OdfLoadingContext.h>
53 #include <CalculationSettings.h>
54 #include <CellStorage.h>
55 #include <HeaderFooter.h>
56 #include <LoadingInfo.h>
57 #include <Map.h>
58 #include <NamedAreaManager.h>
59 #include <RowColumnFormat.h>
60 #include <RowFormatStorage.h>
61 #include <Sheet.h>
62 #include <SheetPrint.h>
63 #include <Style.h>
64 #include <StyleManager.h>
65 #include <StyleStorage.h>
66 #include <ValueConverter.h>
67 #include <ShapeApplicationData.h>
68 #include <Util.h>
69 #include <odf/SheetsOdf.h>
70 
71 #include <Charting.h>
72 #include <KoOdfChartWriter.h>
73 #include <NumberFormatParser.h>
74 
75 #include <iostream>
76 
77 #include "swinder.h"
78 #include "objects.h"
79 #include "ODrawClient.h"
80 #include "ImportUtils.h"
81 #include "conditionals.h"
82 
83 // Enable this definition to make the filter output to an ods file instead of
84 // using m_chain.outputDocument() to write the spreadsheet to.
85 //#define OUTPUT_AS_ODS_FILE
86 
87 K_PLUGIN_FACTORY_WITH_JSON(ExcelImportFactory, "calligra_filter_xls2ods.json", registerPlugin<ExcelImport>();)
88 
89 static const qreal SIDEWINDERPROGRESS = 40.0;
90 static const qreal ODFPROGRESS = 40.0;
91 static const qreal EMBEDDEDPROGRESS = 15.0;
92 
93 using namespace Swinder;
94 using namespace XlsUtils;
95 
offset(unsigned long dimension,unsigned long offset,qreal factor)96 static qreal offset( unsigned long dimension, unsigned long offset, qreal factor ) {
97     return (float)dimension * (float)offset / factor;
98 }
99 
columnWidth(Sheet * sheet,unsigned long col)100 static qreal columnWidth(Sheet* sheet, unsigned long col) {
101     if( sheet->column(col, false) )
102         return sheet->column(col)->width();
103 
104     return sheet->defaultColWidth();
105 }
106 
rowHeight(Sheet * sheet,unsigned long row)107 static qreal rowHeight(Sheet* sheet, unsigned long row) {
108     if( sheet->row(row, false) )
109         return sheet->row(row)->height();
110 
111     return sheet->defaultRowHeight();
112 }
113 
114 class ExcelImport::Private
115 {
116 public:
Private(ExcelImport * q)117     Private(ExcelImport *q)
118     : q(q)
119     {
120     }
121 
122     QString inputFile;
123     Calligra::Sheets::DocBase* outputDoc;
124 
125     Workbook *workbook;
126 
127     // for embedded shapes
128     KoStore* storeout;
129     KoGenStyles *shapeStyles;
130     KoGenStyles *dataStyles;
131     KoXmlWriter *shapesXml;
132 
133     void processMetaData();
134     void processSheet(Sheet* isheet, Calligra::Sheets::Sheet* osheet);
135     void processSheetForHeaderFooter(Sheet* isheet, Calligra::Sheets::Sheet* osheet);
136     void processSheetForFilters(Sheet* isheet, Calligra::Sheets::Sheet* osheet);
137     void processSheetForConditionals(Sheet* isheet, Calligra::Sheets::Sheet* osheet);
138     void processColumn(Sheet* isheet, unsigned column, Calligra::Sheets::Sheet* osheet);
139     void processRow(Sheet* isheet, unsigned row, Calligra::Sheets::Sheet* osheet);
140     void processCell(Cell* icell, Calligra::Sheets::Cell ocell);
141     void processCellObjects(Cell* icell, Calligra::Sheets::Cell ocell);
142     void processEmbeddedObjects(const KoXmlElement& rootElement, KoStore* store);
143     void processNumberFormats();
144 
145     QString convertHeaderFooter(const QString& xlsHeader);
146 
147     int convertStyle(const Format* format, const QString& formula = QString());
148     QHash<CellFormatKey, int> styleCache;
149     QList<Calligra::Sheets::Style> styleList;
150     QHash<QString, Calligra::Sheets::Style> dataStyleCache;
151     QHash<QString, Calligra::Sheets::Conditions> dataStyleConditions;
152 
153     void processFontFormat(const FormatFont& font, Calligra::Sheets::Style& style);
154     QTextCharFormat convertFontToCharFormat(const FormatFont& font);
155     QPen convertBorder(const Pen& pen);
156 
157     int rowsCountTotal, rowsCountDone;
158     void addProgress(int addValue);
159 
160     QHash<int, QRegion> cellStyles;
161     QHash<int, QRegion> rowStyles;
162     QHash<int, QRegion> columnStyles;
163     QList<QPair<QRegion, Calligra::Sheets::Conditions> > cellConditions;
164 
165     QList<KoOdfChartWriter*> charts;
166     void processCharts(KoXmlWriter* manifestWriter);
167 
168     void addManifestEntries(KoXmlWriter* ManifestWriter);
169     void insertPictureManifest(const QString& fileName);
170     QMap<QString,QString> manifestEntries;
171 
172     KoXmlWriter* beginMemoryXmlWriter(const char* docElement);
173     KoXmlDocument endMemoryXmlWriter(KoXmlWriter* writer);
174 
175     QDateTime convertDate(double timestamp) const;
176 
177     ExcelImport *q;
178 
179 };
180 
ExcelImport(QObject * parent,const QVariantList &)181 ExcelImport::ExcelImport(QObject* parent, const QVariantList&)
182         : KoFilter(parent)
183 {
184     d = new Private(this);
185     d->storeout = 0;
186 }
187 
~ExcelImport()188 ExcelImport::~ExcelImport()
189 {
190     delete d->storeout;
191     delete d;
192 }
193 
convert(const QByteArray & from,const QByteArray & to)194 KoFilter::ConversionStatus ExcelImport::convert(const QByteArray& from, const QByteArray& to)
195 {
196     if (from != "application/vnd.ms-excel")
197         return KoFilter::NotImplemented;
198 
199     if (to != "application/vnd.oasis.opendocument.spreadsheet")
200         return KoFilter::NotImplemented;
201 
202     d->inputFile = m_chain->inputFile();
203 
204 #ifndef OUTPUT_AS_ODS_FILE
205     KoDocument* document = m_chain->outputDocument();
206     if (!document)
207         return KoFilter::StupidError;
208 
209     d->outputDoc = qobject_cast<Calligra::Sheets::DocBase*>(document);
210     if (!d->outputDoc) {
211         qCWarning(lcExcelImport) << "document isn't a Calligra::Sheets::Doc but a " << document->metaObject()->className();
212         return KoFilter::WrongFormat;
213     }
214 #else
215     d->outputDoc = new Calligra::Sheets::DocBase();
216 #endif
217     d->outputDoc->setOutputMimeType(to);
218 
219     emit sigProgress(0);
220 
221 
222     QBuffer storeBuffer; // TODO: use temporary file instead
223     delete d->storeout;
224     d->storeout = KoStore::createStore(&storeBuffer, KoStore::Write);
225 
226     // open inputFile
227     d->workbook = new Swinder::Workbook(d->storeout);
228     connect(d->workbook, SIGNAL(sigProgress(int)), this, SLOT(slotSigProgress(int)));
229     if (!d->workbook->load(d->inputFile.toLocal8Bit())) {
230         delete d->workbook;
231         d->workbook = 0;
232         delete d->storeout;
233         d->storeout = 0;
234         return KoFilter::InvalidFormat;
235     }
236 
237     if (d->workbook->isPasswordProtected()) {
238         delete d->workbook;
239         d->workbook = 0;
240         delete d->storeout;
241         d->storeout = 0;
242         return KoFilter::PasswordProtected;
243     }
244 
245     emit sigProgress(-1);
246     emit sigProgress(0);
247 
248     // count the number of rows in total to provide a good progress value
249     d->rowsCountTotal = d->rowsCountDone = 0;
250     for (unsigned i = 0; i < d->workbook->sheetCount(); ++i) {
251         Sheet* sheet = d->workbook->sheet(i);
252         d->rowsCountTotal += qMin(maximalRowCount, sheet->maxRow());
253     }
254 
255     d->shapeStyles = new KoGenStyles();
256     d->dataStyles = new KoGenStyles();
257 
258     // convert number formats
259     d->processNumberFormats();
260 
261     d->processMetaData();
262 
263     d->shapesXml = d->beginMemoryXmlWriter("table:shapes");
264 
265     Calligra::Sheets::Map* map = d->outputDoc->map();
266     for (unsigned i = 0; i < d->workbook->sheetCount(); ++i) {
267         d->shapesXml->startElement("table:table");
268         d->shapesXml->addAttribute("table:id", i);
269         Sheet* sheet = d->workbook->sheet(i);
270         if (i == 0) {
271             map->setDefaultColumnWidth(sheet->defaultColWidth());
272             map->setDefaultRowHeight(sheet->defaultRowHeight());
273         }
274         Calligra::Sheets::Sheet* ksheet = map->addNewSheet(sheet->name());
275         d->processSheet(sheet, ksheet);
276         d->shapesXml->endElement();
277     }
278 
279     // named expressions
280     const std::map<std::pair<unsigned, QString>, QString>& namedAreas = d->workbook->namedAreas();
281     for (std::map<std::pair<unsigned, QString>, QString>::const_iterator it = namedAreas.begin(); it != namedAreas.end(); ++it) {
282         QString range = it->second;
283         if(range.startsWith(QLatin1Char('[')) && range.endsWith(QLatin1Char(']'))) {
284             range.remove(0, 1).chop(1);
285         }
286         Calligra::Sheets::Region region(Calligra::Sheets::Odf::loadRegion(range), d->outputDoc->map());
287         if (!region.isValid() || !region.lastSheet()) {
288             qCDebug(lcExcelImport) << "invalid area" << range;
289             continue;
290         }
291         d->outputDoc->map()->namedAreaManager()->insert(region, it->first.second);
292     }
293 
294     QBuffer manifestBuffer;
295     KoXmlWriter manifestWriter(&manifestBuffer);
296     manifestWriter.startDocument("manifest:manifest");
297     manifestWriter.startElement("manifest:manifest");
298     manifestWriter.addAttribute("xmlns:manifest", KoXmlNS::manifest);
299     manifestWriter.addManifestEntry("/", "application/vnd.oasis.opendocument.spreadsheet");
300 
301     d->processCharts(&manifestWriter);
302     d->addManifestEntries(&manifestWriter);
303 
304     manifestWriter.endElement();
305     manifestWriter.endDocument();
306     if (d->storeout->open("META-INF/manifest.xml")) {
307         d->storeout->write(manifestBuffer.buffer());
308         d->storeout->close();
309     }
310 
311     delete d->storeout;
312     d->storeout = 0;
313     storeBuffer.close();
314 
315     KoStore *store = KoStore::createStore(&storeBuffer, KoStore::Read);
316 
317 
318     // Debug odf for shapes
319 #if 0
320     d->shapesXml->endElement();
321     d->shapesXml->endDocument();
322 
323     d->shapesXml->device()->seek(0);
324 
325     QTextStream input(d->shapesXml->device());
326     qCDebug(lcExcelImport) << "-- START SHAPES_XML -- size : " << d->shapesXml->device()->size();
327     qCDebug(lcExcelImport) << input.readAll();
328     qCDebug(lcExcelImport) << "-- SHAPES_XML --";
329 #endif
330 
331     KoXmlDocument xmlDoc = d->endMemoryXmlWriter(d->shapesXml);
332 
333     d->processEmbeddedObjects(xmlDoc.documentElement(), store);
334 
335     // sheet background images
336     for (unsigned i = 0; i < d->workbook->sheetCount(); ++i) {
337         Sheet* sheet = d->workbook->sheet(i);
338         Calligra::Sheets::Sheet* ksheet = map->sheet(i);
339         qCDebug(lcExcelImport) << i << sheet->backgroundImage();
340         if (sheet->backgroundImage().isEmpty()) continue;
341 
342         QByteArray data;
343         store->extractFile(sheet->backgroundImage(), data);
344         QImage image = QImage::fromData(data);
345         if (image.isNull()) continue;
346 
347         ksheet->setBackgroundImage(image);
348         ksheet->setBackgroundImageProperties(Calligra::Sheets::Sheet::BackgroundImageProperties());
349     }
350 
351 #ifndef OUTPUT_AS_ODS_FILE
352     d->outputDoc->map()->completeLoading(store);
353 #endif
354 
355     delete store;
356 
357     // ensure at least one sheet
358     if (d->outputDoc->map()->count() == 0) {
359         d->outputDoc->map()->addNewSheet();
360     }
361 
362     // active sheet
363     qCDebug(lcExcelImport) << "ACTIVE " << d->workbook->activeTab();
364     d->outputDoc->map()->loadingInfo()->setInitialActiveSheet(d->outputDoc->map()->sheet(d->workbook->activeTab()));
365     d->outputDoc->setModified(false);
366 
367 #ifdef OUTPUT_AS_ODS_FILE
368     d->outputDoc->saveNativeFormat(m_chain->outputFile());
369     delete d->outputDoc;
370 #endif
371 
372     delete d->workbook;
373     delete d->shapeStyles;
374     delete d->dataStyles;
375     d->inputFile.clear();
376     d->outputDoc = 0;
377     d->shapesXml = 0;
378 
379 
380     emit sigProgress(100);
381     return KoFilter::OK;
382 }
383 
processMetaData()384 void ExcelImport::Private::processMetaData()
385 {
386     KoDocumentInfo* info = outputDoc->documentInfo();
387 
388     if (workbook->hasProperty(Workbook::PIDSI_TITLE)) {
389         info->setAboutInfo("title", workbook->property(Workbook::PIDSI_TITLE).toString());
390     }
391     if (workbook->hasProperty(Workbook::PIDSI_SUBJECT)) {
392         info->setAboutInfo("subject", workbook->property(Workbook::PIDSI_SUBJECT).toString());
393     }
394     if (workbook->hasProperty(Workbook::PIDSI_AUTHOR)) {
395         info->setAuthorInfo("creator", workbook->property(Workbook::PIDSI_AUTHOR).toString());
396     }
397     if (workbook->hasProperty(Workbook::PIDSI_KEYWORDS)) {
398         info->setAboutInfo("keyword", workbook->property(Workbook::PIDSI_KEYWORDS).toString());
399     }
400     if (workbook->hasProperty(Workbook::PIDSI_COMMENTS)) {
401         info->setAboutInfo("comments", workbook->property(Workbook::PIDSI_COMMENTS).toString());
402     }
403     if (workbook->hasProperty(Workbook::PIDSI_REVNUMBER)) {
404         info->setAboutInfo("editing-cycles", workbook->property(Workbook::PIDSI_REVNUMBER).toString());
405     }
406     if (workbook->hasProperty(Workbook::PIDSI_LASTPRINTED_DTM)) {
407         info->setAboutInfo("print-date", workbook->property(Workbook::PIDSI_LASTPRINTED_DTM).toString());
408     }
409     if (workbook->hasProperty(Workbook::PIDSI_CREATE_DTM)) {
410         info->setAboutInfo("creation-date", workbook->property(Workbook::PIDSI_CREATE_DTM).toString());
411     }
412     if (workbook->hasProperty(Workbook::PIDSI_LASTSAVED_DTM)) {
413         info->setAboutInfo("date", workbook->property(Workbook::PIDSI_LASTSAVED_DTM).toString());
414     }
415     // template
416     // lastauthor
417     // edittime
418 
419     switch (workbook->version()) {
420     case Workbook::Excel95:
421         info->setOriginalGenerator("Calligra xls Filter/Excel 95");
422         break;
423     case Workbook::Excel97:
424         info->setOriginalGenerator("Calligra xls Filter/Excel 97");
425         break;
426     case Workbook::Excel2000:
427         info->setOriginalGenerator("Calligra xls Filter/Excel 2000");
428         break;
429     case Workbook::Excel2002:
430         info->setOriginalGenerator("Calligra xls Filter/Excel 2002");
431         break;
432     case Workbook::Excel2003:
433         info->setOriginalGenerator("Calligra xls Filter/Excel 2003");
434         break;
435     case Workbook::Excel2007:
436         info->setOriginalGenerator("Calligra xls Filter/Excel 2007");
437         break;
438     case Workbook::Excel2010:
439         info->setOriginalGenerator("Calligra xls Filter/Excel 2010");
440         break;
441     default:
442         info->setOriginalGenerator("Calligra xls Filter/Unknown");
443     }
444 }
445 
processEmbeddedObjects(const KoXmlElement & rootElement,KoStore * store)446 void ExcelImport::Private::processEmbeddedObjects(const KoXmlElement& rootElement, KoStore* store)
447 {
448     // save styles to xml
449     KoXmlWriter *stylesXml = beginMemoryXmlWriter("office:styles");
450     shapeStyles->saveOdfStyles(KoGenStyles::DocumentAutomaticStyles, stylesXml);
451 
452     KoXmlDocument stylesDoc = endMemoryXmlWriter(stylesXml);
453 
454     // Register additional attributes, that identify shapes anchored in cells.
455     // Their dimensions need adjustment after all rows are loaded,
456     // because the position of the end cell is not always known yet.
457     KoShapeLoadingContext::addAdditionalAttributeData(KoShapeLoadingContext::AdditionalAttributeData(
458                 KoXmlNS::table, "end-cell-address",
459                 "table:end-cell-address"));
460     KoShapeLoadingContext::addAdditionalAttributeData(KoShapeLoadingContext::AdditionalAttributeData(
461                 KoXmlNS::table, "end-x",
462                 "table:end-x"));
463     KoShapeLoadingContext::addAdditionalAttributeData(KoShapeLoadingContext::AdditionalAttributeData(
464                 KoXmlNS::table, "end-y",
465                 "table:end-y"));
466 
467 
468     KoOdfStylesReader odfStyles;
469     odfStyles.createStyleMap(stylesDoc, false);
470     KoOdfLoadingContext odfContext(odfStyles, store);
471     KoShapeLoadingContext shapeContext(odfContext, outputDoc->resourceManager());
472 
473     int numSheetTotal = rootElement.childNodesCount();
474     int currentSheet = 0;
475     KoXmlElement sheetElement;
476     forEachElement(sheetElement, rootElement) {
477         Q_ASSERT(sheetElement.namespaceURI() == KoXmlNS::table && sheetElement.localName() == "table");
478         int sheetId = sheetElement.attributeNS(KoXmlNS::table, "id").toInt();
479         Calligra::Sheets::Sheet* sheet = outputDoc->map()->sheet(sheetId);
480 
481         KoXmlElement cellElement;
482         int numCellElements = sheetElement.childNodesCount();
483         int currentCell = 0;
484         forEachElement(cellElement, sheetElement) {
485             Q_ASSERT(cellElement.namespaceURI() == KoXmlNS::table);
486             if (cellElement.localName() == "shapes") {
487                 KoXmlElement element;
488                 forEachElement(element, cellElement) {
489                     Calligra::Sheets::Odf::loadSheetObject(sheet, element, shapeContext);
490                 }
491             } else {
492                 Q_ASSERT(cellElement.localName() == "table-cell");
493                 int row = cellElement.attributeNS(KoXmlNS::table, "row").toInt();
494                 int col = cellElement.attributeNS(KoXmlNS::table, "column").toInt();
495                 Calligra::Sheets::Cell cell(sheet, col, row);
496 
497                 KoXmlElement element;
498                 forEachElement(element, cellElement) {
499                     Calligra::Sheets::Odf::loadObject(&cell, element, shapeContext);
500                 }
501             }
502             ++currentCell;
503             const int progress = int(currentSheet / qreal(numSheetTotal) * EMBEDDEDPROGRESS
504                                + (EMBEDDEDPROGRESS / qreal(numSheetTotal) * currentCell/numCellElements)
505                                + SIDEWINDERPROGRESS + ODFPROGRESS) + 0.5;
506             emit q->sigProgress(progress);
507         }
508 
509         ++currentSheet;
510         const int progress = int(currentSheet / qreal(numSheetTotal) * EMBEDDEDPROGRESS + SIDEWINDERPROGRESS + ODFPROGRESS + 0.5);
511         emit q->sigProgress(progress);
512     }
513 }
514 
getRect(const MSO::OfficeArtFSPGR & r)515 static QRectF getRect(const MSO::OfficeArtFSPGR &r)
516 {
517     return QRect(r.xLeft, r.yTop, r.xRight - r.xLeft, r.yBottom - r.yTop);
518 }
519 
processSheet(Sheet * is,Calligra::Sheets::Sheet * os)520 void ExcelImport::Private::processSheet(Sheet* is, Calligra::Sheets::Sheet* os)
521 {
522     os->setHidden(!is->visible());
523     //os->setProtected(is->protect());
524     os->setAutoCalculationEnabled(is->autoCalc());
525     os->setHideZero(!is->showZeroValues());
526     os->setShowGrid(is->showGrid());
527     os->setFirstLetterUpper(false);
528     os->map()->loadingInfo()->setCursorPosition(os, is->firstVisibleCell() + QPoint(1, 1));
529     os->setShowFormulaIndicator(false);
530     os->setShowCommentIndicator(true);
531     os->setShowPageOutline(is->isPageBreakViewEnabled());
532     os->setLcMode(false);
533     os->setShowColumnNumber(false);
534     os->setLayoutDirection(is->isRightToLeft() ? Qt::RightToLeft : Qt::LeftToRight);
535 
536     // TODO: page layout
537     processSheetForHeaderFooter(is, os);
538 
539     if(is->password() != 0) {
540         //TODO
541     }
542 
543 
544     const unsigned columnCount = qMin(maximalColumnCount, is->maxColumn());
545     for (unsigned i = 0; i <= columnCount; ++i) {
546         processColumn(is, i, os);
547     }
548 
549     cellStyles.clear();
550     rowStyles.clear();
551     columnStyles.clear();
552     cellConditions.clear();
553     const unsigned rowCount = qMin(maximalRowCount, is->maxRow());
554     for (unsigned i = 0; i <= rowCount && i < KS_rowMax; ++i) {
555         processRow(is, i, os);
556     }
557 
558     QList<QPair<QRegion, Calligra::Sheets::Style> > styles;
559     for (QHash<int, QRegion>::const_iterator it = columnStyles.constBegin(); it != columnStyles.constEnd(); ++it) {
560         styles.append(qMakePair(it.value(), styleList[it.key()]));
561     }
562     for (QHash<int, QRegion>::const_iterator it = rowStyles.constBegin(); it != rowStyles.constEnd(); ++it) {
563         styles.append(qMakePair(it.value(), styleList[it.key()]));
564     }
565     for (QHash<int, QRegion>::const_iterator it = cellStyles.constBegin(); it != cellStyles.constEnd(); ++it) {
566         styles.append(qMakePair(it.value(), styleList[it.key()]));
567     }
568     os->cellStorage()->loadStyles(styles);
569 
570     // sheet shapes
571     if (!is->drawObjects().isEmpty() || is->drawObjectsGroupCount()) {
572         shapesXml->startElement("table:shapes");
573 
574         ODrawClient client = ODrawClient(is);
575         ODrawToOdf odraw(client);
576         Writer writer(*shapesXml, *shapeStyles, false);
577 
578         const QList<OfficeArtObject*> objs = is->drawObjects();
579         for (int i = 0; i < objs.size(); ++i) {
580             OfficeArtObject* o = objs[i];
581             client.setShapeText(o->text());
582             client.setZIndex(o->index());
583             client.setStyleManager(outputDoc->map()->textStyleManager());
584             odraw.processDrawingObject(o->object(), writer);
585         }
586 
587         for (int i = is->drawObjectsGroupCount()-1; i >= 0; --i) {
588             shapesXml->startElement("draw:g");
589 
590             const MSO::OfficeArtSpgrContainer& group = is->drawObjectsGroup(i);
591             const MSO::OfficeArtSpContainer* first = group.rgfb.first().anon.get<MSO::OfficeArtSpContainer>();
592             if (first && first->clientAnchor && first->shapeGroup) {
593                 QRectF oldCoords = client.getGlobalRect(*first->clientAnchor);
594                 QRectF newCoords = getRect(*first->shapeGroup);
595                 Writer transw = writer.transform(oldCoords, newCoords);
596                 const QList<OfficeArtObject*> gobjs = is->drawObjects(i);
597                 for (int j = 0; j < gobjs.size(); ++j) {
598                     OfficeArtObject* o = gobjs[j];
599                     client.setShapeText(o->text());
600                     client.setZIndex(o->index());
601                     client.setStyleManager(outputDoc->map()->textStyleManager());
602                     odraw.processDrawingObject(o->object(), transw);
603                 }
604             } else {
605                 const QList<OfficeArtObject*> gobjs = is->drawObjects(i);
606                 for (int j = 0; j < gobjs.size(); ++j) {
607                     OfficeArtObject* o = gobjs[j];
608                     client.setShapeText(o->text());
609                     client.setZIndex(o->index());
610                     client.setStyleManager(outputDoc->map()->textStyleManager());
611                     odraw.processDrawingObject(o->object(), writer);
612                 }
613             }
614             shapesXml->endElement(); // draw:g
615         }
616 
617         shapesXml->endElement();
618     }
619 
620 
621     processSheetForFilters(is, os);
622     processSheetForConditionals(is, os);
623 
624     os->cellStorage()->loadConditions(cellConditions);
625 }
626 
processSheetForHeaderFooter(Sheet * is,Calligra::Sheets::Sheet * os)627 void ExcelImport::Private::processSheetForHeaderFooter(Sheet* is, Calligra::Sheets::Sheet* os)
628 {
629     os->print()->headerFooter()->setHeadFootLine(
630             convertHeaderFooter(is->leftHeader()), convertHeaderFooter(is->centerHeader()),
631             convertHeaderFooter(is->rightHeader()), convertHeaderFooter(is->leftFooter()),
632             convertHeaderFooter(is->centerFooter()), convertHeaderFooter(is->rightFooter()));
633 }
634 
processSheetForFilters(Sheet * is,Calligra::Sheets::Sheet * os)635 void ExcelImport::Private::processSheetForFilters(Sheet* is, Calligra::Sheets::Sheet* os)
636 {
637     static int rangeId = 0; // not very nice to do this this way, but I only care about sort of unique names
638     QList<QRect> filters = workbook->filterRanges(is);
639     foreach (const QRect& filter, filters) {
640         Calligra::Sheets::Database db;
641         db.setName(QString("excel-database-%1").arg(++rangeId));
642         db.setDisplayFilterButtons(true);
643         QRect r = filter.adjusted(1, 1, 1, 1);
644         r.setBottom(is->maxRow()+1);
645         Calligra::Sheets::Region range(r, os);
646         db.setRange(range);
647         db.setFilter(is->autoFilters());
648         os->cellStorage()->setDatabase(range, db);
649 
650         // xls files don't seem to make a difference between hidden and filtered rows, so
651         // assume all rows in a database range are filtered, not explicitly hidden
652         int row = r.top() + 1;
653         while (row <= r.bottom()) {
654             int lastRow;
655             bool isHidden = os->rowFormats()->isHidden(row, &lastRow);
656             if (isHidden) {
657                 os->rowFormats()->setHidden(row, lastRow, false);
658                 os->rowFormats()->setFiltered(row, lastRow, true);
659             }
660             row = lastRow + 1;
661         }
662     }
663 }
664 
convertValue(const Value & v)665 static Calligra::Sheets::Value convertValue(const Value& v)
666 {
667     if (v.isBoolean()) {
668         return Calligra::Sheets::Value(v.asBoolean());
669     } else if (v.isFloat()) {
670         return Calligra::Sheets::Value(v.asFloat());
671     } else if (v.isInteger()) {
672         return Calligra::Sheets::Value(v.asInteger());
673     } else if (v.isText()) {
674         return Calligra::Sheets::Value(v.asString());
675     } else if (v.isError()) {
676         Calligra::Sheets::Value kv(Calligra::Sheets::Value::Error);
677         kv.setError(v.asString());
678         return kv;
679     } else {
680         return Calligra::Sheets::Value();
681     }
682 }
683 
processSheetForConditionals(Sheet * is,Calligra::Sheets::Sheet * os)684 void ExcelImport::Private::processSheetForConditionals(Sheet* is, Calligra::Sheets::Sheet* os)
685 {
686     static int styleNameId = 0;
687     const QList<ConditionalFormat*> conditionals = is->conditionalFormats();
688     Calligra::Sheets::StyleManager* styleManager = os->map()->styleManager();
689     foreach (ConditionalFormat* cf, conditionals) {
690         QRegion r = cf->region().translated(1, 1);
691         QLinkedList<Calligra::Sheets::Conditional> conds;
692         foreach (const Conditional& c, cf->conditionals()) {
693             Calligra::Sheets::Conditional kc;
694             switch (c.cond) {
695             case Conditional::None:
696                 kc.cond = Calligra::Sheets::Conditional::None;
697                 break;
698             case Conditional::Formula:
699                 kc.cond = Calligra::Sheets::Conditional::IsTrueFormula;
700                 break;
701             case Conditional::Between:
702                 kc.cond = Calligra::Sheets::Conditional::Between;
703                 break;
704             case Conditional::Outside:
705                 kc.cond = Calligra::Sheets::Conditional::Different;
706                 break;
707             case Conditional::Equal:
708                 kc.cond = Calligra::Sheets::Conditional::Equal;
709                 break;
710             case Conditional::NotEqual:
711                 kc.cond = Calligra::Sheets::Conditional::DifferentTo;
712                 break;
713             case Conditional::Greater:
714                 kc.cond = Calligra::Sheets::Conditional::Superior;
715                 break;
716             case Conditional::Less:
717                 kc.cond = Calligra::Sheets::Conditional::Inferior;
718                 break;
719             case Conditional::GreaterOrEqual:
720                 kc.cond = Calligra::Sheets::Conditional::SuperiorEqual;
721                 break;
722             case Conditional::LessOrEqual:
723                 kc.cond = Calligra::Sheets::Conditional::InferiorEqual;
724                 break;
725             }
726             qCDebug(lcExcelImport) << "FRM:" << c.cond << kc.cond;
727             kc.value1 = convertValue(c.value1);
728             kc.value2 = convertValue(c.value2);
729             kc.baseCellAddress = Swinder::encodeAddress(is->name(), cf->region().boundingRect().left(), cf->region().boundingRect().top());
730 
731             Calligra::Sheets::CustomStyle* style = new Calligra::Sheets::CustomStyle(QString("Excel-Condition-Style-%1").arg(styleNameId++));
732             kc.styleName = style->name();
733 
734             // TODO: valueFormat
735             if (c.hasFontItalic()) {
736                 style->setFontItalic(c.font().italic());
737             }
738             if (c.hasFontStrikeout()) {
739                 style->setFontStrikeOut(c.font().strikeout());
740             }
741             if (c.hasFontBold()) {
742                 style->setFontBold(c.font().bold());
743             }
744             // TODO: sub/superscript
745             if (c.hasFontUnderline()) {
746                 style->setFontUnderline(c.font().underline());
747             }
748             if (c.hasFontColor()) {
749                 style->setFontColor(c.font().color());
750             }
751             // TODO: other properties
752 
753             styleManager->insertStyle(style);
754             conds.append(kc);
755         }
756         Calligra::Sheets::Conditions kcs;
757         kcs.setConditionList(conds);
758         cellConditions.append(qMakePair(r, kcs));
759     }
760 }
761 
convertHeaderFooter(const QString & text)762 QString ExcelImport::Private::convertHeaderFooter(const QString& text)
763 {
764     QString result;
765     bool skipUnsupported = false;
766     int lastPos;
767     int pos = text.indexOf('&');
768     int len = text.length();
769     if ((pos < 0) && (text.length() > 0))   // If there is no &
770         result += text;
771     else if (pos > 0) // Some text and '&'
772         result += text.mid(0,  pos - 1);
773 
774     while (pos >= 0) {
775         switch (text[pos + 1].unicode()) {
776         case 'D':
777             result += "<date>";
778             break;
779         case 'T':
780             result += "<time>";
781             break;
782         case 'P':
783             result += "<page>";
784             break;
785         case 'N':
786             result += "<pages>";
787             break;
788         case 'F':
789             result += "<name>";
790             break;
791         case 'A':
792             result += "<sheet>";
793             break;
794         case '\"':
795         default:
796             skipUnsupported = true;
797             break;
798         }
799         lastPos = pos;
800         pos = text.indexOf('&', lastPos + 1);
801         if (!skipUnsupported && (pos > (lastPos + 1)))
802             result += text.mid(lastPos + 2, (pos - lastPos - 2));
803         else if (!skipUnsupported && (pos < 0))  //Remaining text
804             result += text.mid(lastPos + 2, len - (lastPos + 2));
805         else
806             skipUnsupported = false;
807     }
808     return result;
809 }
810 
processColumn(Sheet * is,unsigned columnIndex,Calligra::Sheets::Sheet * os)811 void ExcelImport::Private::processColumn(Sheet* is, unsigned columnIndex, Calligra::Sheets::Sheet* os)
812 {
813     Column* column = is->column(columnIndex, false);
814 
815     if (!column) return;
816 
817     Calligra::Sheets::ColumnFormat* oc = os->nonDefaultColumnFormat(columnIndex+1);
818     oc->setWidth(column->width());
819     oc->setHidden(!column->visible());
820 
821     int styleId = convertStyle(&column->format());
822     columnStyles[styleId] += QRect(columnIndex+1, 1, 1, KS_rowMax);
823 }
824 
processRow(Sheet * is,unsigned rowIndex,Calligra::Sheets::Sheet * os)825 void ExcelImport::Private::processRow(Sheet* is, unsigned rowIndex, Calligra::Sheets::Sheet* os)
826 {
827     Row *row = is->row(rowIndex, false);
828 
829     if (!row) {
830         if (is->defaultRowHeight() != os->map()->defaultRowFormat()->height()) {
831             os->rowFormats()->setRowHeight(rowIndex+1, rowIndex+1, is->defaultRowHeight());
832         }
833         return;
834     }
835 
836     os->rowFormats()->setRowHeight(rowIndex+1, rowIndex+1, row->height());
837     os->rowFormats()->setHidden(rowIndex+1, rowIndex+1, !row->visible());
838     // TODO default cell style
839 
840     // find the column of the rightmost cell (if any)
841     const int lastCol = row->sheet()->maxCellsInRow(rowIndex);
842     for (int i = 0; i <= lastCol; ++i) {
843         Cell* cell = is->cell(i, rowIndex, false);
844         if (!cell) continue;
845         processCell(cell, Calligra::Sheets::Cell(os, i+1, rowIndex+1));
846     }
847 
848     addProgress(1);
849 }
850 
cellFormulaNamespace(const QString & formula)851 static QString cellFormulaNamespace(const QString& formula)
852 {
853     if (!formula.isEmpty()) {
854         if(formula.startsWith("ROUNDUP(") || formula.startsWith("ROUNDDOWN(") || formula.startsWith("ROUND(") || formula.startsWith("RAND(")) {
855             // Special case Excel formulas that differ from OpenFormula
856             return "msoxl:";
857         } else if (!formula.isEmpty()) {
858             return "of:";
859         }
860     }
861     return QString();
862 }
863 
convertDate(double timestamp) const864 QDateTime ExcelImport::Private::convertDate(double timestamp) const
865 {
866     QDateTime dt(workbook->baseDate());
867     dt = dt.addMSecs((qint64)(timestamp * 86400 * 1000));
868     return dt;
869 }
870 
convertTime(double timestamp)871 static QTime convertTime(double timestamp)
872 {
873     QTime tt;
874     tt = tt.addMSecs(qRound((timestamp - (qint64)timestamp) * 86400 * 1000));
875     return tt;
876 }
877 
processCell(Cell * ic,Calligra::Sheets::Cell oc)878 void ExcelImport::Private::processCell(Cell* ic, Calligra::Sheets::Cell oc)
879 {
880     int colSpan = ic->columnSpan();
881     int rowSpan = ic->rowSpan();
882     if (colSpan > 1 || rowSpan > 1) {
883         oc.mergeCells(oc.column(), oc.row(), colSpan - 1, rowSpan - 1);
884     }
885 
886     const QString formula = ic->formula();
887     const bool isFormula = !formula.isEmpty();
888     if (isFormula) {
889         const QString nsPrefix = cellFormulaNamespace(formula);
890         const QString decodedFormula = Calligra::Sheets::Odf::decodeFormula('=' + formula, oc.locale(), nsPrefix);
891         oc.setRawUserInput(decodedFormula);
892     }
893 
894     int styleId = convertStyle(&ic->format(), formula);
895 
896     Value value = ic->value();
897     if (value.isBoolean()) {
898         oc.setValue(Calligra::Sheets::Value(value.asBoolean()));
899         if (!isFormula)
900             oc.setRawUserInput(oc.sheet()->map()->converter()->asString(oc.value()).asString());
901     } else if (value.isNumber()) {
902         const QString valueFormat = ic->format().valueFormat();
903 
904         if (isPercentageFormat(valueFormat)) {
905             Calligra::Sheets::Value v(value.asFloat());
906             v.setFormat(Calligra::Sheets::Value::fmt_Percent);
907             oc.setValue(v);
908         } else if (Calligra::Sheets::Format::isDate(styleList[styleId].formatType())) {
909             QDateTime date = convertDate(value.asFloat());
910             oc.setValue(Calligra::Sheets::Value(date, outputDoc->map()->calculationSettings()));
911             KLocale* locale = outputDoc->map()->calculationSettings()->locale();
912             if (!isFormula) {
913                 if (true /* TODO somehow determine if time should be included */) {
914                     oc.setRawUserInput(locale->formatDate(date.date()));
915                 } else {
916                     oc.setRawUserInput(locale->formatDateTime(date));
917                 }
918             }
919         } else if (Calligra::Sheets::Format::isTime(styleList[styleId].formatType())) {
920             QTime time = convertTime(value.asFloat());
921             oc.setValue(Calligra::Sheets::Value(time));
922             KLocale* locale = outputDoc->map()->calculationSettings()->locale();
923             if (!isFormula)
924                 oc.setRawUserInput(locale->formatTime(time, true));
925         } else /* fraction or normal */ {
926             oc.setValue(Calligra::Sheets::Value(value.asFloat()));
927             if (!isFormula)
928                 oc.setRawUserInput(oc.sheet()->map()->converter()->asString(oc.value()).asString());
929         }
930     } else if (value.isText()) {
931         QString txt = value.asString();
932 
933         Hyperlink link = ic->hyperlink();
934         if (link.isValid) {
935             if (!link.location.isEmpty()) {
936                 if (link.location[0] == '#') {
937                     oc.setLink(link.location.mid(1));
938                 } else {
939                     oc.setLink(link.location);
940                 }
941 
942                 if (!link.displayName.trimmed().isEmpty())
943                     txt = link.displayName.trimmed();
944             }
945         }
946 
947         oc.setValue(Calligra::Sheets::Value(txt));
948         if (!isFormula) {
949             if (txt.startsWith('='))
950                 oc.setRawUserInput('\'' + txt);
951             else
952                 oc.setRawUserInput(txt);
953         }
954         if (value.isRichText() || ic->format().font().subscript() || ic->format().font().superscript()) {
955             std::map<unsigned, FormatFont> formatRuns = value.formatRuns();
956             // add sentinel to list of format runs
957             if (!formatRuns.count(0))
958                 formatRuns[0] = ic->format().font();
959             formatRuns[txt.length()] = ic->format().font();
960 
961             QSharedPointer<QTextDocument> doc(new QTextDocument(txt));
962             KoTextDocument(doc.data()).setStyleManager(oc.sheet()->map()->textStyleManager());
963             QTextCursor c(doc.data());
964             for (std::map<unsigned, FormatFont>::iterator it = formatRuns.begin(); it != formatRuns.end(); ++it) {
965                 std::map<unsigned, FormatFont>::iterator it2 = it; ++it2;
966                 if (it2 != formatRuns.end()) {
967                     // select block
968                     c.movePosition(QTextCursor::Right, QTextCursor::KeepAnchor, it2->first - it->first);
969                     c.setCharFormat(convertFontToCharFormat(it->second));
970                     c.clearSelection();
971                 }
972             }
973             oc.setRichText(doc);
974         }
975     } else if (value.isError()) {
976         Calligra::Sheets::Value v(Calligra::Sheets::Value::Error);
977         v.setError(value.asString());
978         oc.setValue(v);
979     }
980 
981     QString note = ic->note();
982     if (!note.isEmpty())
983         oc.setComment(note);
984 
985     cellStyles[styleId] += QRect(oc.column(), oc.row(), 1, 1);
986     QHash<QString, Calligra::Sheets::Conditions>::ConstIterator conds = dataStyleConditions.constFind(ic->format().valueFormat());
987     if (conds != dataStyleConditions.constEnd()) {
988         cellConditions.append(qMakePair(QRegion(oc.column(), oc.row(), 1, 1), conds.value()));
989     }
990 
991     processCellObjects(ic, oc);
992 }
993 
processCellObjects(Cell * ic,Calligra::Sheets::Cell oc)994 void ExcelImport::Private::processCellObjects(Cell* ic, Calligra::Sheets::Cell oc)
995 {
996     bool hasObjects = false;
997 
998     // handle charts
999     foreach(ChartObject *chart, ic->charts()) {
1000         Sheet* const sheet = ic->sheet();
1001         if(chart->m_chart->m_impl==0) {
1002             qCDebug(lcExcelImport) << "Invalid chart to be created, no implementation.";
1003             continue;
1004         }
1005 
1006         if (!hasObjects) {
1007             shapesXml->startElement("table:table-cell");
1008             shapesXml->addAttribute("table:row", oc.row());
1009             shapesXml->addAttribute("table:column", oc.column());
1010             hasObjects = true;
1011         }
1012 
1013         KoOdfChartWriter *c = new KoOdfChartWriter(chart->m_chart);
1014         c->setSheetReplacement( false );
1015         c->m_href = QString("Chart%1").arg(this->charts.count()+1);
1016         c->m_endCellAddress = Swinder::encodeAddress(sheet->name(), chart->m_colR, chart->m_rwB);
1017         c->m_end_x = offset(columnWidth(sheet, chart->m_colR), chart->m_dxR, 1024);
1018         c->m_end_y = offset(columnWidth(sheet, chart->m_rwB), chart->m_dyB, 256);
1019         c->m_notifyOnUpdateOfRanges = "Sheet1.D2:Sheet1.F2"; //TODO don't hardcode!
1020 
1021         const unsigned long colL = chart->m_colL;
1022         const unsigned long dxL = chart->m_dxL;
1023         const unsigned long dyT = chart->m_dyT;
1024         const unsigned long rwT = chart->m_rwT;
1025 
1026         c->m_x = offset(columnWidth(sheet, colL), dxL, 1024);
1027         c->m_y = offset(rowHeight(sheet, rwT), dyT, 256);
1028 
1029         if (!chart->m_chart->m_cellRangeAddress.isNull() )
1030             c->m_cellRangeAddress = Swinder::encodeAddress(sheet->name(), chart->m_chart->m_cellRangeAddress.left(), chart->m_chart->m_cellRangeAddress.top()) + ":" +
1031                                     Swinder::encodeAddress(sheet->name(), chart->m_chart->m_cellRangeAddress.right(), chart->m_chart->m_cellRangeAddress.bottom());
1032 
1033         this->charts << c;
1034 
1035         c->saveIndex(shapesXml);
1036     }
1037 
1038 
1039 
1040     // handle ODraw objects
1041     QList<OfficeArtObject*> objects = ic->drawObjects();
1042     if (!objects.empty()) {
1043         if (!hasObjects) {
1044             shapesXml->startElement("table:table-cell");
1045             shapesXml->addAttribute("table:row", oc.row());
1046             shapesXml->addAttribute("table:column", oc.column());
1047             hasObjects = true;
1048         }
1049         ODrawClient client = ODrawClient(ic->sheet());
1050 
1051         ODrawToOdf odraw(client);
1052         Writer writer(*shapesXml, *shapeStyles, false);
1053         for (int i = 0; i < objects.size(); ++i) {
1054             OfficeArtObject* o = objects[i];
1055             client.setShapeText(o->text());
1056             client.setZIndex(o->index());
1057             client.setStyleManager(outputDoc->map()->textStyleManager());
1058             odraw.processDrawingObject(o->object(), writer);
1059         }
1060     }
1061 
1062     if (hasObjects) {
1063         shapesXml->endElement();
1064     }
1065 }
1066 
processCharts(KoXmlWriter * manifestWriter)1067 void ExcelImport::Private::processCharts(KoXmlWriter* manifestWriter)
1068 {
1069     foreach(KoOdfChartWriter *c, this->charts) {
1070         c->set2003ColorPalette( workbook->colorTable() );
1071         c->saveContent(this->storeout, manifestWriter);
1072     }
1073 }
1074 
convertStyle(const Format * format,const QString & formula)1075 int ExcelImport::Private::convertStyle(const Format* format, const QString& formula)
1076 {
1077     CellFormatKey key(format, formula);
1078     int& styleId = styleCache[key];
1079     if (!styleId) {
1080         Calligra::Sheets::Style style;
1081         style.setDefault();
1082 
1083         if (!key.isGeneral) {
1084             style.merge(dataStyleCache.value(format->valueFormat(), Calligra::Sheets::Style()));
1085         } else {
1086             if (key.decimalCount >= 0) {
1087                 style.setFormatType(Calligra::Sheets::Format::Number);
1088                 style.setPrecision(key.decimalCount);
1089                 QString format = ".";
1090                 for (int i = 0; i < key.decimalCount; i++) {
1091                     format += '0';
1092                 }
1093                 style.setCustomFormat(format);
1094             }
1095         }
1096 
1097         processFontFormat(format->font(), style);
1098 
1099         FormatAlignment align = format->alignment();
1100         if (!align.isNull()) {
1101             switch (align.alignY()) {
1102             case Format::Top:
1103                 style.setVAlign(Calligra::Sheets::Style::Top);
1104                 break;
1105             case Format::Middle:
1106                 style.setVAlign(Calligra::Sheets::Style::Middle);
1107                 break;
1108             case Format::Bottom:
1109                 style.setVAlign(Calligra::Sheets::Style::Bottom);
1110                 break;
1111             case Format::VJustify:
1112                 style.setVAlign(Calligra::Sheets::Style::VJustified);
1113                 break;
1114             case Format::VDistributed:
1115                 style.setVAlign(Calligra::Sheets::Style::VDistributed);
1116                 break;
1117             }
1118 
1119             style.setWrapText(align.wrap());
1120 
1121             if (align.rotationAngle()) {
1122                 style.setAngle(align.rotationAngle());
1123             }
1124 
1125             if (align.stackedLetters()) {
1126                 style.setVerticalText(true);
1127             }
1128 
1129             if (align.shrinkToFit()) {
1130                 style.setShrinkToFit(true);
1131             }
1132 
1133             switch (align.alignX()) {
1134             case Format::Left:
1135                 style.setHAlign(Calligra::Sheets::Style::Left);
1136                 break;
1137             case Format::Center:
1138                 style.setHAlign(Calligra::Sheets::Style::Center);
1139                 break;
1140             case Format::Right:
1141                 style.setHAlign(Calligra::Sheets::Style::Right);
1142                 break;
1143             case Format::Justify:
1144             case Format::Distributed:
1145                 style.setHAlign(Calligra::Sheets::Style::Justified);
1146                 break;
1147             }
1148 
1149             if (align.indentLevel() != 0) {
1150                 style.setIndentation(align.indentLevel() * 10);
1151             }
1152         }
1153 
1154         FormatBorders borders = format->borders();
1155         if (!borders.isNull()) {
1156             style.setLeftBorderPen(convertBorder(borders.leftBorder()));
1157             style.setRightBorderPen(convertBorder(borders.rightBorder()));
1158             style.setTopBorderPen(convertBorder(borders.topBorder()));
1159             style.setBottomBorderPen(convertBorder(borders.bottomBorder()));
1160             style.setFallDiagonalPen(convertBorder(borders.topLeftBorder()));
1161             style.setGoUpDiagonalPen(convertBorder(borders.bottomLeftBorder()));
1162         }
1163 
1164         FormatBackground back = format->background();
1165         if (!back.isNull() && back.pattern() != FormatBackground::EmptyPattern) {
1166             QColor backColor = back.backgroundColor();
1167             if (back.pattern() == FormatBackground::SolidPattern)
1168                 backColor = back.foregroundColor();
1169             style.setBackgroundColor(backColor);
1170 
1171             QBrush brush;
1172             switch (back.pattern()) {
1173             case FormatBackground::SolidPattern:
1174                 brush.setStyle(Qt::SolidPattern);
1175                 brush.setColor(backColor);
1176                 break;
1177             case FormatBackground::Dense3Pattern: // 88% gray
1178                 brush.setStyle(Qt::Dense2Pattern);
1179                 brush.setColor(Qt::black);
1180                 break;
1181             case FormatBackground::Dense4Pattern: // 50% gray
1182                 brush.setStyle(Qt::Dense4Pattern);
1183                 brush.setColor(Qt::black);
1184                 break;
1185             case FormatBackground::Dense5Pattern: // 37% gray
1186                 brush.setStyle(Qt::Dense5Pattern);
1187                 brush.setColor(Qt::black);
1188                 break;
1189             case FormatBackground::Dense6Pattern: // 12% gray
1190                 brush.setStyle(Qt::Dense6Pattern);
1191                 brush.setColor(Qt::black);
1192                 break;
1193             case FormatBackground::Dense7Pattern: // 6% gray
1194                 brush.setStyle(Qt::Dense7Pattern);
1195                 brush.setColor(Qt::black);
1196                 break;
1197 
1198             case FormatBackground::Dense1Pattern:
1199             case FormatBackground::HorPattern:
1200                 brush.setStyle(Qt::HorPattern);
1201                 brush.setColor(Qt::black);
1202                 break;
1203             case FormatBackground::VerPattern:
1204                 brush.setStyle(Qt::VerPattern);
1205                 brush.setColor(Qt::black);
1206                 break;
1207             case FormatBackground::Dense2Pattern:
1208             case FormatBackground::BDiagPattern:
1209                 brush.setStyle(Qt::BDiagPattern);
1210                 brush.setColor(Qt::black);
1211                 break;
1212             case FormatBackground::FDiagPattern:
1213                 brush.setStyle(Qt::FDiagPattern);
1214                 brush.setColor(Qt::black);
1215                 break;
1216             case FormatBackground::CrossPattern:
1217                 brush.setStyle(Qt::CrossPattern);
1218                 brush.setColor(Qt::black);
1219                 break;
1220             case FormatBackground::DiagCrossPattern:
1221                 brush.setStyle(Qt::DiagCrossPattern);
1222                 brush.setColor(Qt::black);
1223                 break;
1224             }
1225             style.setBackgroundBrush(brush);
1226         }
1227 
1228         styleId = styleList.size();
1229         styleList.append(style);
1230     }
1231     return styleId;
1232 }
1233 
processFontFormat(const FormatFont & font,Calligra::Sheets::Style & style)1234 void ExcelImport::Private::processFontFormat(const FormatFont& font, Calligra::Sheets::Style& style)
1235 {
1236     if (font.isNull()) return;
1237 
1238     QFont f;
1239     f.setBold(font.bold());
1240     f.setItalic(font.italic());
1241     f.setUnderline(font.underline());
1242     f.setStrikeOut(font.strikeout());
1243     f.setFamily(font.fontFamily());
1244     f.setPointSizeF(font.fontSize());
1245     style.setFont(f);
1246     style.setFontColor(font.color());
1247 }
1248 
convertFontToCharFormat(const FormatFont & font)1249 QTextCharFormat ExcelImport::Private::convertFontToCharFormat(const FormatFont& font)
1250 {
1251     QTextCharFormat frm;
1252     QFont f;
1253     f.setBold(font.bold());
1254     f.setItalic(font.italic());
1255     f.setUnderline(font.underline());
1256     f.setStrikeOut(font.strikeout());
1257     f.setFamily(font.fontFamily());
1258     f.setPointSizeF(font.fontSize());
1259     frm.setFont(f);
1260     frm.setForeground(font.color());
1261     if (font.subscript())
1262         frm.setVerticalAlignment(QTextCharFormat::AlignSubScript);
1263     if (font.superscript())
1264         frm.setVerticalAlignment(QTextCharFormat::AlignSuperScript);
1265     return frm;
1266 }
1267 
convertBorder(const Pen & pen)1268 QPen ExcelImport::Private::convertBorder(const Pen& pen)
1269 {
1270     if (pen.style == Pen::NoLine || pen.width == 0) {
1271         return QPen(Qt::NoPen);
1272     } else {
1273         QPen op;
1274         if (pen.style == Pen::DoubleLine) {
1275             op.setWidthF(pen.width * 3);
1276         } else {
1277             op.setWidthF(pen.width);
1278         }
1279 
1280         switch (pen.style) {
1281         case Pen::SolidLine: op.setStyle(Qt::SolidLine); break;
1282         case Pen::DashLine: op.setStyle(Qt::DashLine); break;
1283         case Pen::DotLine: op.setStyle(Qt::DotLine); break;
1284         case Pen::DashDotLine: op.setStyle(Qt::DashDotLine); break;
1285         case Pen::DashDotDotLine: op.setStyle(Qt::DashDotDotLine); break;
1286         case Pen::DoubleLine: op.setStyle(Qt::SolidLine); break; // TODO
1287         }
1288 
1289         op.setColor(pen.color);
1290 
1291         return op;
1292     }
1293 }
1294 
insertPictureManifest(const QString & fileName)1295 void ExcelImport::Private::insertPictureManifest(const QString& fileName)
1296 {
1297     QString mimeType;
1298     const QString extension = fileName.right(fileName.size() - fileName.lastIndexOf('.') - 1);
1299 
1300     if( extension == "gif" ) {
1301         mimeType = "image/gif";
1302     }
1303     else if( extension == "jpg" || extension == "jpeg"
1304             || extension == "jpe" || extension == "jfif" ) {
1305         mimeType = "image/jpeg";
1306     }
1307     else if( extension == "tif" || extension == "tiff" ) {
1308         mimeType = "image/tiff";
1309     }
1310     else if( extension == "png" ) {
1311         mimeType = "image/png";
1312     }
1313     else if( extension == "emf" ) {
1314         mimeType = "application/x-openoffice-wmf;windows_formatname=\"Image EMF\"";
1315     }
1316     else if( extension == "wmf" ) {
1317         mimeType = "application/x-openoffice-wmf;windows_formatname=\"Image WMF\"";
1318     }
1319     else if( extension == "bmp" ) {
1320         mimeType = "image/bmp";
1321     }
1322 
1323     manifestEntries.insert("Pictures/" + fileName, mimeType);
1324 }
1325 
addManifestEntries(KoXmlWriter * manifestWriter)1326 void ExcelImport::Private::addManifestEntries(KoXmlWriter* manifestWriter)
1327 {
1328     QMap<QString, QString>::const_iterator iterator = manifestEntries.constBegin();
1329     QMap<QString, QString>::const_iterator end = manifestEntries.constEnd();
1330     while( iterator != end ) {
1331         manifestWriter->addManifestEntry(iterator.key(), iterator.value());
1332         ++iterator;
1333     }
1334 }
1335 
1336 
1337 // Updates the displayed progress information
addProgress(int addValue)1338 void ExcelImport::Private::addProgress(int addValue)
1339 {
1340     rowsCountDone += addValue;
1341     const int progress = int(rowsCountDone / qreal(rowsCountTotal) * ODFPROGRESS + 0.5 + SIDEWINDERPROGRESS);
1342     emit q->sigProgress(progress);
1343 }
1344 
beginMemoryXmlWriter(const char * docElement)1345 KoXmlWriter* ExcelImport::Private::beginMemoryXmlWriter(const char* docElement)
1346 {
1347     QIODevice* d = new QBuffer;
1348     d->open(QIODevice::ReadWrite);
1349     KoXmlWriter* xml = new KoXmlWriter(d);
1350     xml->startDocument(docElement);
1351     xml->startElement(docElement);
1352     xml->addAttribute("xmlns:office", KoXmlNS::office);
1353     xml->addAttribute("xmlns:meta", KoXmlNS::meta);
1354     xml->addAttribute("xmlns:config", KoXmlNS::config);
1355     xml->addAttribute("xmlns:text", KoXmlNS::text);
1356     xml->addAttribute("xmlns:table", KoXmlNS::table);
1357     xml->addAttribute("xmlns:draw", KoXmlNS::draw);
1358     xml->addAttribute("xmlns:presentation", KoXmlNS::presentation);
1359     xml->addAttribute("xmlns:dr3d", KoXmlNS::dr3d);
1360     xml->addAttribute("xmlns:chart", KoXmlNS::chart);
1361     xml->addAttribute("xmlns:form", KoXmlNS::form);
1362     xml->addAttribute("xmlns:script", KoXmlNS::script);
1363     xml->addAttribute("xmlns:style", KoXmlNS::style);
1364     xml->addAttribute("xmlns:number", KoXmlNS::number);
1365     xml->addAttribute("xmlns:math", KoXmlNS::math);
1366     xml->addAttribute("xmlns:svg", KoXmlNS::svg);
1367     xml->addAttribute("xmlns:fo", KoXmlNS::fo);
1368     xml->addAttribute("xmlns:anim", KoXmlNS::anim);
1369     xml->addAttribute("xmlns:smil", KoXmlNS::smil);
1370     xml->addAttribute("xmlns:calligra", KoXmlNS::calligra);
1371     xml->addAttribute("xmlns:officeooo", KoXmlNS::officeooo);
1372     xml->addAttribute("xmlns:dc", KoXmlNS::dc);
1373     xml->addAttribute("xmlns:xlink", KoXmlNS::xlink);
1374     return xml;
1375 }
1376 
endMemoryXmlWriter(KoXmlWriter * writer)1377 KoXmlDocument ExcelImport::Private::endMemoryXmlWriter(KoXmlWriter* writer)
1378 {
1379     writer->endElement();
1380     writer->endDocument();
1381     QBuffer* b = static_cast<QBuffer*>(writer->device());
1382 
1383 
1384     b->seek(0);
1385     KoXmlDocument doc;
1386     QString errorMsg; int errorLine, errorColumn;
1387     if (!doc.setContent(b, true, &errorMsg, &errorLine, &errorColumn)) {
1388         qCDebug(lcExcelImport) << errorMsg << errorLine << errorColumn;
1389     }
1390     delete b;
1391     delete writer;
1392     return doc;
1393 }
1394 
processNumberFormats()1395 void ExcelImport::Private::processNumberFormats()
1396 {
1397     static const QString sNoStyle = QString::fromLatin1("NOSTYLE");
1398     QHash<QString, QString> dataStyleMap;
1399 
1400     for (int i = 0; i < workbook->formatCount(); i++) {
1401         Format* f = workbook->format(i);
1402         QString& styleName = dataStyleMap[f->valueFormat()];
1403         if (styleName.isEmpty()) {
1404             KoGenStyle s = NumberFormatParser::parse(f->valueFormat(), dataStyles);
1405             if (s.type() != KoGenStyle::ParagraphAutoStyle) {
1406                 styleName = dataStyles->insert(s, "N");
1407             } else {
1408                 styleName = sNoStyle; // assign it a name anyway to prevent converting it twice
1409             }
1410         }
1411     }
1412 
1413     KoXmlWriter *stylesXml = beginMemoryXmlWriter("office:styles");
1414     dataStyles->saveOdfStyles(KoGenStyles::DocumentAutomaticStyles, stylesXml);
1415 
1416     KoXmlDocument stylesDoc = endMemoryXmlWriter(stylesXml);
1417 
1418     KoOdfStylesReader odfStyles;
1419     odfStyles.createStyleMap(stylesDoc, false);
1420 
1421     for (int i = 0; i < workbook->formatCount(); i++) {
1422         Format* f = workbook->format(i);
1423         const QString& styleName = dataStyleMap[f->valueFormat()];
1424         if (styleName != sNoStyle) {
1425             Calligra::Sheets::Style& style = dataStyleCache[f->valueFormat()];
1426             if (style.isEmpty()) {
1427                 Calligra::Sheets::Conditions conditions;
1428                 Calligra::Sheets::Odf::loadDataStyle(&style, odfStyles, styleName, conditions, outputDoc->map()->styleManager(), outputDoc->map()->parser());
1429 
1430                 if (!conditions.isEmpty())
1431                     dataStyleConditions[f->valueFormat()] = conditions;
1432             }
1433         }
1434     }
1435 }
1436 
slotSigProgress(int progress)1437 void ExcelImport::slotSigProgress(int progress)
1438 {
1439     emit sigProgress(int(SIDEWINDERPROGRESS/100.0 * progress + 0.5));
1440 }
1441 
1442 #include "ExcelImport.moc"
1443