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 <excelimporttoods.h>
25 
26 #include <QString>
27 #include <QDate>
28 #include <QBuffer>
29 #include <QFontMetricsF>
30 #include <QPair>
31 #include <KoFilterChain.h>
32 #include <kpluginfactory.h>
33 
34 #include <KoXmlWriter.h>
35 #include <KoOdfWriteStore.h>
36 #include <KoGenStyles.h>
37 #include <KoGenStyle.h>
38 
39 #include <Charting.h>
40 #include <KoOdfChartWriter.h>
41 #include <NumberFormatParser.h>
42 
43 #include "swinder.h"
44 #include "objects.h"
45 #include <iostream>
46 #include "ODrawClient.h"
47 #include "ImportUtils.h"
48 #include "writeodf/writeodfofficedc.h"
49 #include "writeodf/writeodfofficemeta.h"
50 #include "writeodf/writeodfofficestyle.h"
51 #include "writeodf/writeodfofficetable.h"
52 #include "writeodf/writeodftext.h"
53 #include "writeodf/writeodfnumber.h"
54 #include "writeodf/helpers.h"
55 
56 K_PLUGIN_FACTORY(ExcelImportFactory, registerPlugin<ExcelImport>();)
57 K_EXPORT_PLUGIN(ExcelImportFactory("calligrafilters"))
58 
59 #define UNICODE_EUR 0x20AC
60 #define UNICODE_GBP 0x00A3
61 #define UNICODE_JPY 0x00A5
62 
63 using namespace writeodf;
64 
urlFromArg(const QString & arg)65 QUrl urlFromArg(const QString& arg)
66 {
67 #if QT_VERSION >= 0x050400
68     return QUrl::fromUserInput(arg, QDir::currentPath(), QUrl::AssumeLocalFile);
69 #else
70     // Logic from QUrl::fromUserInput(QString, QString, UserInputResolutionOptions)
71     return (QUrl(arg, QUrl::TolerantMode).isRelative() && !QDir::isAbsolutePath(arg))
72            ? QUrl::fromLocalFile(QDir::current().absoluteFilePath(arg))
73            : QUrl::fromUserInput(arg);
74 #endif
75 }
76 
77 namespace Swinder
78 {
79 // qHash function to support hashing by Swinder::FormatFont instances.
qHash(const Swinder::FormatFont & font)80 static inline uint qHash(const Swinder::FormatFont& font)
81 {
82     // TODO: make this a better hash
83     return qHash(font.fontFamily()) ^ qRound(font.fontSize() * 100);
84 }
85 
offset(unsigned long dimension,unsigned long offset,qreal factor)86 static qreal offset( unsigned long dimension, unsigned long offset, qreal factor ) {
87     return (float)dimension * (float)offset / factor;
88 }
89 
columnWidth(Sheet * sheet,unsigned long col)90 static qreal columnWidth(Sheet* sheet, unsigned long col) {
91     if( sheet->column(col, false) )
92         return sheet->column(col)->width();
93 
94     return sheet->defaultColWidth();
95 }
96 
rowHeight(Sheet * sheet,unsigned long row)97 static qreal rowHeight(Sheet* sheet, unsigned long row) {
98     if( sheet->row(row, false) )
99         return sheet->row(row)->height();
100 
101     return sheet->defaultRowHeight();
102 }
103 
104 }
105 
106 using namespace Swinder;
107 using namespace XlsUtils;
108 
109 class ExcelImport::Private
110 {
111 public:
112     QString inputFile;
113     QString outputFile;
114 
115     KoStore* storeout;
116     Workbook *workbook;
117 
118     KoGenStyles *styles;
119     KoGenStyles *mainStyles;
120     QList<QString> cellStyles;
121     QList<QString> rowStyles;
122     QList<QString> colStyles;
123     QList<QString> colCellStyles;
124     QList<QString> sheetStyles;
125     QHash<FormatFont, QString> fontStyles;
126     QString subScriptStyle, superScriptStyle;
127     QHash<QString, KoGenStyle> valueFormatCache;
128     QHash<CellFormatKey, QString> cellFormatCache;
129     QList<KoOdfChartWriter*> charts;
130     QHash<Cell*, QByteArray> cellShapes;
131     QHash<Sheet*, QByteArray> sheetShapes;
132 
133     struct CellValue {
134         Value value;
135         QString str;
136         QString linkName;
137         QString linkLocation;
138         Hyperlink link;
139     };
140 
141     QHash<Row*,int> rowsRepeatedHash;
142     int rowsRepeated(Row* row, int rowIndex);
143 
144     int rowsCountTotal, rowsCountDone;
145     void addProgress(int addValue);
146 
147     bool createStyles(KoStore* store, KoXmlWriter* manifestWriter, KoGenStyles* mainStyles);
148     bool createContent(KoOdfWriteStore* store);
149     bool createMeta(KoOdfWriteStore* store);
150     bool createSettings(KoOdfWriteStore* store);
151 
152     int sheetFormatIndex;
153     int columnFormatIndex;
154     int rowFormatIndex;
155     int cellFormatIndex;
156 
157     void processWorkbookForBody(Workbook* workbook, KoXmlWriter* xmlWriter, office_body& body);
158     void processWorkbookForStyle(Workbook* workbook, KoXmlWriter* xmlWriter);
159     void processSheetForBody(Sheet* sheet, KoXmlWriter* xmlWriter, office_spreadsheet& spreadsheet);
160     void processSheetForStyle(Sheet* sheet, KoXmlWriter* xmlWriter);
161     void processSheetForHeaderFooter(Sheet* sheet, KoXmlWriter* writer);
162     void processHeaderFooterStyle(const QString& text, text_p& p);
163     void processColumnForBody(Sheet* sheet, int columnIndex, group_table_columns_and_groups& table, unsigned& outlineLevel);
164     void processColumnForStyle(Sheet* sheet, int columnIndex, KoXmlWriter* xmlWriter);
165     int processRowForBody(Sheet* sheet, int rowIndex, KoXmlWriter* xmlWriter, group_table_rows_and_groups& table, unsigned& outlineLevel);
166     int processRowForStyle(Sheet* sheet, int rowIndex, KoXmlWriter* xmlWriter);
167     void processCellForBody(Cell* cell, KoXmlWriter* xmlWriter, table_table_row& row);
168     void processCellAttributesForBody(Cell* cell, group_table_table_cell_attlist& c, CellValue& cellValue);
169     void processCellText(Cell* cell, group_paragraph_content& content, CellValue& cellValue);
170     void processCellContentForBody(Cell* cell, KoXmlWriter* xmlWriter, group_table_table_cell_content& c, CellValue& cellValue);
171     void processCellForStyle(Cell* cell, KoXmlWriter* xmlWriter);
172     QString processCellFormat(const Format* format, const QString& formula = QString());
173     QString processRowFormat(Format* format, const QString& breakBefore = QString(), int rowRepeat = 1, double rowHeight = -1);
174     void processFormat(const Format* format, KoGenStyle& style);
175     QString processValueFormat(const QString& valueFormat);
176     void processFontFormat(const FormatFont& font, KoGenStyle& style, bool allProps = false);
177     void processCharts(KoXmlWriter* manifestWriter);
178 
179     void createDefaultColumnStyle( Sheet* sheet );
180     void processSheetBackground(Sheet* sheet, KoGenStyle& style);
181     void addManifestEntries(KoXmlWriter* ManifestWriter);
182     void insertPictureManifest(const QString &fileName);
183 
184     bool isDateFormat(const QString& valueFormat);
185 
186     QList<QString> defaultColumnStyles;
187     int defaultColumnStyleIndex;
188     QMap<QString,QString> manifestEntries;
189 };
190 
ExcelImport(QObject * parent,const QVariantList &)191 ExcelImport::ExcelImport(QObject* parent, const QVariantList&)
192         : KoFilter(parent)
193 {
194     d = new Private;
195 }
196 
~ExcelImport()197 ExcelImport::~ExcelImport()
198 {
199     delete d;
200 }
201 
convert(const QByteArray & from,const QByteArray & to)202 KoFilter::ConversionStatus ExcelImport::convert(const QByteArray& from, const QByteArray& to)
203 {
204     if (from != "application/vnd.ms-excel")
205         return KoFilter::NotImplemented;
206 
207     if (to != "application/vnd.oasis.opendocument.spreadsheet")
208         return KoFilter::NotImplemented;
209 
210     d->inputFile = m_chain->inputFile();
211     d->outputFile = m_chain->outputFile();
212 
213     // create output store
214     d->storeout = KoStore::createStore(d->outputFile, KoStore::Write,
215                                     "application/vnd.oasis.opendocument.spreadsheet", KoStore::Zip);
216     if (!d->storeout || d->storeout->bad()) {
217         qCWarning(lcExcelImport) << "Couldn't open the requested file.";
218         delete d->workbook;
219         delete d->storeout;
220         return KoFilter::FileNotFound;
221     }
222 
223     emit sigProgress(0);
224 
225     // open inputFile
226     d->workbook = new Swinder::Workbook(d->storeout);
227     connect(d->workbook, SIGNAL(sigProgress(int)), this, SIGNAL(sigProgress(int)));
228     if (!d->workbook->load(d->inputFile.toLocal8Bit())) {
229         delete d->workbook;
230         d->workbook = 0;
231         return KoFilter::StupidError;
232     }
233 
234     if (d->workbook->isPasswordProtected()) {
235         delete d->workbook;
236         d->workbook = 0;
237         return KoFilter::PasswordProtected;
238     }
239 
240     emit sigProgress(-1);
241     emit sigProgress(0);
242 
243     d->styles = new KoGenStyles();
244     d->mainStyles = new KoGenStyles();
245 
246     KoOdfWriteStore oasisStore(d->storeout);
247     KoXmlWriter* manifestWriter = oasisStore.manifestWriter("application/vnd.oasis.opendocument.spreadsheet");
248 
249     // header and footer are read from each sheet and saved in styles
250     // So creating content before styles
251     // store document content
252     if (!d->createContent(&oasisStore)) {
253         qCWarning(lcExcelImport) << "Couldn't open the file 'content.xml'.";
254         delete d->workbook;
255         delete d->storeout;
256         return KoFilter::CreationError;
257     }
258 
259     // store document styles
260     if (!d->createStyles(d->storeout, manifestWriter, d->mainStyles)) {
261         qCWarning(lcExcelImport) << "Couldn't open the file 'styles.xml'.";
262         delete d->workbook;
263         delete d->storeout;
264         return KoFilter::CreationError;
265     }
266 
267     // store meta content
268     if (!d->createMeta(&oasisStore)) {
269         qCWarning(lcExcelImport) << "Couldn't open the file 'meta.xml'.";
270         delete d->workbook;
271         delete d->storeout;
272         return KoFilter::CreationError;
273     }
274 
275     // store settings
276     if (!d->createSettings(&oasisStore)) {
277         qCWarning(lcExcelImport) << "Couldn't open the file 'settings.xml'.";
278         delete d->workbook;
279         delete d->storeout;
280         return KoFilter::CreationError;
281     }
282 
283     manifestWriter->addManifestEntry("meta.xml", "text/xml");
284     manifestWriter->addManifestEntry("styles.xml", "text/xml");
285     manifestWriter->addManifestEntry("content.xml", "text/xml");
286     manifestWriter->addManifestEntry("settings.xml", "text/xml");
287 
288     d->processCharts(manifestWriter);
289     d->addManifestEntries(manifestWriter);
290     oasisStore.closeManifestWriter();
291 
292     // we are done!
293     delete d->workbook;
294     delete d->styles;
295     delete d->mainStyles;
296     delete d->storeout;
297     d->inputFile.clear();
298     d->outputFile.clear();
299     d->workbook = 0;
300     d->styles = 0;
301     d->mainStyles = 0;
302     d->cellStyles.clear();
303     d->rowStyles.clear();
304     d->colStyles.clear();
305     d->colCellStyles.clear();
306     d->sheetStyles.clear();
307 
308     emit sigProgress(100);
309     return KoFilter::OK;
310 }
311 
312 // Updates the displayed progress information
addProgress(int addValue)313 void ExcelImport::Private::addProgress(int addValue)
314 {
315     rowsCountDone += addValue;
316     const int progress = int(rowsCountDone / double(rowsCountTotal) * 100.0 + 0.5);
317     workbook->emitProgress(progress);
318 }
319 
rowsRepeated(Row * row,int rowIndex)320 int ExcelImport::Private::rowsRepeated(Row* row, int rowIndex)
321 {
322     if(rowsRepeatedHash.contains(row))
323         return rowsRepeatedHash[row];
324     // a row does usually at least repeat itself
325     int repeat = 1;
326     // find the column of the rightmost cell (if any)
327     int lastCol = row->sheet()->maxCellsInRow(rowIndex);
328     // find repeating rows by forward searching
329     const unsigned rowCount = qMin(maximalRowCount, row->sheet()->maxRow());
330     for (unsigned i = rowIndex + 1; i <= rowCount; ++i) {
331         Row *nextRow = row->sheet()->row(i, false);
332         if(!nextRow) break;
333         if (*row != *nextRow) break; // do the rows have the same properties?
334         const int nextLastCol = row->sheet()->maxCellsInRow(i);
335         if (lastCol != nextLastCol) break;
336         bool cellsAreSame = true;
337         for(int c = 0; c <= lastCol; ++c) {
338             Cell* c1 = row->sheet()->cell(c, row->index(), false);
339             Cell* c2 = nextRow->sheet()->cell(c, nextRow->index(), false);
340             if (!c1 != !c2 || (c1 && *c1 != *c2)) {
341                 cellsAreSame = false;
342                 break; // job done, abort loop
343             }
344         }
345         if (!cellsAreSame) break;
346         ++repeat;
347     }
348     rowsRepeatedHash[row] = repeat; // cache the result
349     return repeat;
350 }
351 
352 // Writes the spreadsheet content into the content.xml
createContent(KoOdfWriteStore * store)353 bool ExcelImport::Private::createContent(KoOdfWriteStore* store)
354 {
355     KoXmlWriter* bodyWriter = store->bodyWriter();
356     KoXmlWriter* contentWriter = store->contentWriter();
357     if (!bodyWriter || !contentWriter)
358         return false;
359 
360     if(workbook->password() != 0) {
361         contentWriter->addAttribute("table:structure-protected-excel", "true");
362         contentWriter->addAttribute("table:protection-key-excel" , uint(workbook->password()));
363     }
364 
365     // FIXME this is dummy and hardcoded, replace with real font names
366     office_font_face_decls decls(contentWriter);
367     style_font_face font(decls.add_style_font_face("Arial"));
368     font.set_svg_font_family("Arial");
369     style_font_face font2(decls.add_style_font_face("Times New Roman"));
370     font2.set_svg_font_family("&apos;Times New Roman&apos;");
371     decls.end();
372 
373     defaultColumnStyleIndex = 0;
374     // office:automatic-styles
375     processWorkbookForStyle(workbook, contentWriter);
376     styles->saveOdfStyles(KoGenStyles::DocumentAutomaticStyles, contentWriter);
377 
378     // important: reset all indexes
379     sheetFormatIndex = 0;
380     columnFormatIndex = 0;
381     rowFormatIndex = 0;
382     cellFormatIndex = 0;
383 
384 
385     // office:body
386     office_body body(bodyWriter);
387     processWorkbookForBody(workbook, bodyWriter, body);
388     body.end();
389 
390     return store->closeContentWriter();
391 }
392 
393 
394 
395 // Writes the styles.xml
createStyles(KoStore * store,KoXmlWriter * manifestWriter,KoGenStyles * mainStyles)396 bool ExcelImport::Private::createStyles(KoStore* store, KoXmlWriter* manifestWriter, KoGenStyles* mainStyles)
397 {
398     Q_UNUSED(manifestWriter);
399     if (!store->open("styles.xml"))
400         return false;
401     KoStoreDevice dev(store);
402     KoXmlWriter* stylesWriter = new KoXmlWriter(&dev);
403 
404     stylesWriter->startDocument("office:document-styles");
405     office_document_styles styles(stylesWriter);
406     styles.addAttribute("xmlns:office", "urn:oasis:names:tc:opendocument:xmlns:office:1.0");
407     styles.addAttribute("xmlns:style", "urn:oasis:names:tc:opendocument:xmlns:style:1.0");
408     styles.addAttribute("xmlns:text", "urn:oasis:names:tc:opendocument:xmlns:text:1.0");
409     styles.addAttribute("xmlns:table", "urn:oasis:names:tc:opendocument:xmlns:table:1.0");
410     styles.addAttribute("xmlns:draw", "urn:oasis:names:tc:opendocument:xmlns:drawing:1.0");
411     styles.addAttribute("xmlns:fo", "urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0");
412     styles.addAttribute("xmlns:svg", "urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0");
413     styles.addAttribute("xmlns:xlink", "http://www.w3.org/1999/xlink");
414     styles.addAttribute("xmlns:chart", "urn:oasis:names:tc:opendocument:xmlns:chart:1.0");
415     styles.addAttribute("xmlns:dc", "http://purl.org/dc/elements/1.1/");
416     styles.addAttribute("xmlns:meta", "urn:oasis:names:tc:opendocument:xmlns:meta:1.0");
417     styles.addAttribute("xmlns:number", "urn:oasis:names:tc:opendocument:xmlns:datastyle:1.0");
418     //styles.addAttribute("xmlns:dr3d", "urn:oasis:names:tc:opendocument:xmlns:dr3d:1.0");
419     styles.addAttribute("xmlns:math", "http://www.w3.org/1998/Math/MathML");
420     styles.addAttribute("xmlns:of", "urn:oasis:names:tc:opendocument:xmlns:of:1.2");
421 
422     mainStyles->saveOdfStyles(KoGenStyles::MasterStyles, stylesWriter);
423     mainStyles->saveOdfStyles(KoGenStyles::DocumentStyles, stylesWriter); // office:style
424     mainStyles->saveOdfStyles(KoGenStyles::DocumentAutomaticStyles, stylesWriter); // office:automatic-styles
425 
426     styles.end();
427     stylesWriter->endDocument();
428 
429     delete stylesWriter;
430     return store->close();
431 }
432 
433 // Writes meta-information into the meta.xml
createMeta(KoOdfWriteStore * store)434 bool ExcelImport::Private::createMeta(KoOdfWriteStore* store)
435 {
436     if (!store->store()->open("meta.xml"))
437         return false;
438 
439     KoStoreDevice dev(store->store());
440     KoXmlWriter* metaWriter = new KoXmlWriter(&dev);
441     metaWriter->startDocument("office:document-meta");
442 
443     office_document_meta metadoc(metaWriter);
444     metadoc.addAttribute("xmlns:office", "urn:oasis:names:tc:opendocument:xmlns:office:1.0");
445     metadoc.addAttribute("xmlns:xlink", "http://www.w3.org/1999/xlink");
446     metadoc.addAttribute("xmlns:dc", "http://purl.org/dc/elements/1.1/");
447     metadoc.addAttribute("xmlns:meta", "urn:oasis:names:tc:opendocument:xmlns:meta:1.0");
448     office_meta meta(metadoc.add_office_meta());
449 
450     if (workbook->hasProperty(Workbook::PIDSI_TITLE)) {
451         meta.add_dc_title().addTextNode(
452                     workbook->property(Workbook::PIDSI_TITLE).toString());
453     }
454     if (workbook->hasProperty(Workbook::PIDSI_SUBJECT)) {
455         meta.add_dc_subject().addTextNode(
456                     workbook->property(Workbook::PIDSI_SUBJECT).toString());
457     }
458     if (workbook->hasProperty(Workbook::PIDSI_AUTHOR)) {
459         meta.add_dc_creator().addTextNode(
460                     workbook->property(Workbook::PIDSI_AUTHOR).toString());
461     }
462     if (workbook->hasProperty(Workbook::PIDSI_KEYWORDS)) {
463         meta.add_meta_keyword().addTextNode(
464                     workbook->property(Workbook::PIDSI_KEYWORDS).toString());
465     }
466     if (workbook->hasProperty(Workbook::PIDSI_COMMENTS)) {
467         meta_user_defined c(meta.add_meta_user_defined("comments"));
468         c.set_meta_value_type("string");
469         c.addTextNode(
470                     workbook->property(Workbook::PIDSI_COMMENTS).toString());
471     }
472     if (workbook->hasProperty(Workbook::PIDSI_REVNUMBER)) {
473         meta.add_meta_editing_cycles().addTextNode(
474                     workbook->property(Workbook::PIDSI_REVNUMBER).toString());
475     }
476     if (workbook->hasProperty(Workbook::PIDSI_LASTPRINTED_DTM)) {
477         meta.add_meta_print_date().addTextNode(
478                     workbook->property(Workbook::PIDSI_LASTPRINTED_DTM).toString());
479     }
480     if (workbook->hasProperty(Workbook::PIDSI_CREATE_DTM)) {
481         meta.add_meta_creation_date().addTextNode(
482                     workbook->property(Workbook::PIDSI_CREATE_DTM).toString());
483     }
484     if (workbook->hasProperty(Workbook::PIDSI_LASTSAVED_DTM)) {
485         meta.add_dc_date().addTextNode(
486                     workbook->property(Workbook::PIDSI_LASTSAVED_DTM).toString());
487     }
488 
489     //if( workbook->hasProperty( Workbook::PIDSI_TEMPLATE )  ) metaWriter->addAttribute( "dc:", workbook->property( Workbook::PIDSI_TEMPLATE ).toString() );
490     //if( workbook->hasProperty( Workbook::PIDSI_LASTAUTHOR )  ) metaWriter->addAttribute( "dc:", workbook->property( Workbook::PIDSI_LASTAUTHOR ).toString() );
491     //if( workbook->hasProperty( Workbook::PIDSI_EDITTIME )  ) metaWriter->addAttribute( "dc:date", workbook->property( Workbook::PIDSI_EDITTIME ).toString() );
492 
493     metadoc.end();
494     metaWriter->endDocument();
495 
496     delete metaWriter;
497     return store->store()->close();
498 }
499 
500 // Writes configuration-settings into the settings.xml
createSettings(KoOdfWriteStore * store)501 bool ExcelImport::Private::createSettings(KoOdfWriteStore* store)
502 {
503     if (!store->store()->open("settings.xml"))
504         return false;
505 
506     KoStoreDevice dev(store->store());
507     KoXmlWriter* settingsWriter = KoOdfWriteStore::createOasisXmlWriter(&dev, "office:document-settings");
508     {
509     office_settings settings(settingsWriter);
510     config_config_item_set set(settings.add_config_config_item_set("view-settings"));
511 
512     // units...
513 
514     // settings
515     config_config_item_map_indexed map(set.add_config_config_item_map_indexed("Views"));
516     config_config_item_map_entry entry(map.add_config_config_item_map_entry());
517 
518     addConfigItem(entry, "ViewId", QString::fromLatin1("View1"));
519     if(Sheet *sheet = workbook->sheet(workbook->activeTab()))
520             addConfigItem(entry, "ActiveTable", sheet->name());
521 
522     config_config_item_map_named named(entry.add_config_config_item_map_named("Tables"));
523     for(uint i = 0; i < workbook->sheetCount(); ++i) {
524         Sheet* sheet = workbook->sheet(i);
525         config_config_item_map_entry entry(named.add_config_config_item_map_entry());
526         entry.set_config_name(sheet->name());
527         QPoint point = sheet->firstVisibleCell();
528         addConfigItem(entry, "CursorPositionX", point.x());
529         addConfigItem(entry, "CursorPositionY", point.y());
530         //TODO how should we replace these settings?
531 //         settingsWriter->addConfigItem("xOffset", columnWidth(sheet,point.x()));
532 //         settingsWriter->addConfigItem("yOffset", rowHeight(sheet,point.y()));
533         addConfigItem(entry, "ShowZeroValues", sheet->showZeroValues());
534         addConfigItem(entry, "ShowGrid", sheet->showGrid());
535         addConfigItem(entry, "FirstLetterUpper", false);
536         addConfigItem(entry, "ShowFormulaIndicator", false);
537         addConfigItem(entry, "ShowCommentIndicator", true);
538         addConfigItem(entry, "ShowPageOutline", sheet->isPageBreakViewEnabled()); // best match Sheets provides
539         addConfigItem(entry, "lcmode", false);
540         addConfigItem(entry, "autoCalc", sheet->autoCalc());
541         addConfigItem(entry, "ShowColumnNumber", false);
542     }
543     } // end of block closes all elements
544     settingsWriter->endDocument();
545     delete settingsWriter;
546     return store->store()->close();
547 }
548 
549 // Processes the workbook content. The workbook is the top-level element for content.
processWorkbookForBody(Workbook * workbook,KoXmlWriter * xmlWriter,office_body & body)550 void ExcelImport::Private::processWorkbookForBody(Workbook* workbook, KoXmlWriter* xmlWriter, office_body& body)
551 {
552     if (!workbook) return;
553     if (!xmlWriter) return;
554 
555     office_spreadsheet spreadsheet(body.add_office_spreadsheet());
556 
557     table_calculation_settings calcsettings = spreadsheet.add_table_calculation_settings();
558     calcsettings.set_table_case_sensitive(false);
559     calcsettings.set_table_automatic_find_labels(false);
560     calcsettings.set_table_use_regular_expressions(false);
561     calcsettings.set_table_use_wildcards(true);
562 
563     // count the number of rows in total to provide a good progress value
564     rowsCountTotal = rowsCountDone = 0;
565     for (unsigned i = 0; i < workbook->sheetCount(); ++i) {
566         Sheet* sheet = workbook->sheet(i);
567         rowsCountTotal += qMin(maximalRowCount, sheet->maxRow()) * 2; // double cause we will count them 2 times, once for styles and once for content
568     }
569 
570     // now start the whole work
571     for (unsigned i = 0; i < workbook->sheetCount(); ++i) {
572         Sheet* sheet = workbook->sheet(i);
573         processSheetForBody(sheet, xmlWriter, spreadsheet);
574     }
575 
576     std::map<std::pair<unsigned, QString>, QString> &namedAreas = workbook->namedAreas();
577     if(namedAreas.size() > 0) {
578         table_named_expressions exprs(spreadsheet.add_table_named_expressions());
579         for(std::map<std::pair<unsigned, QString>, QString>::iterator it = namedAreas.begin(); it != namedAreas.end(); ++it) {
580             QString range = it->second;
581             if(range.startsWith(QLatin1Char('[')) && range.endsWith(QLatin1Char(']'))) {
582                 range.remove(0, 1).chop(1);
583             }
584             table_named_range(exprs.add_table_named_range(range, it->first.second));
585         }
586     }
587 
588     table_database_ranges ranges(spreadsheet.add_table_database_ranges());
589     int rangeId = 1;
590     for (unsigned i = 0; i < workbook->sheetCount(); ++i) {
591         QList<QRect> filters = workbook->filterRanges(i);
592         QString sheetName = workbook->sheet(i)->name();
593         if (filters.size()) {
594             foreach (const QRect& filter, filters) {
595                 QString sRange(encodeAddress(sheetName, filter.left(), filter.top()));
596                 sRange.append(":");
597                 sRange.append(encodeAddress(sheetName, filter.right(), workbook->sheet(i)->maxRow()));
598                 table_database_range range(ranges.add_table_database_range(sRange));
599                 range.set_table_name(QString("excel-database-%1").arg(rangeId++));
600                 range.set_table_display_filter_buttons("true");
601             }
602         }
603     }
604 }
605 
606 // Processes the workbook styles. The workbook is the top-level element for content.
processWorkbookForStyle(Workbook * workbook,KoXmlWriter * xmlWriter)607 void ExcelImport::Private::processWorkbookForStyle(Workbook* workbook, KoXmlWriter* xmlWriter)
608 {
609     if (!workbook) return;
610     if (!xmlWriter) return;
611 
612     QString contentElement;
613     QString masterStyleName("Default");
614     QString pageLayoutStyleName("Mpm");
615 
616     KoGenStyle pageLayoutStyle(KoGenStyle::PageLayoutStyle);
617     pageLayoutStyle.addProperty("style:writing-mode", "lr-tb");
618 
619     QBuffer buf;
620     buf.open(QIODevice::WriteOnly);
621     KoXmlWriter writer(&buf);
622 
623     //Hardcoded page-layout
624     style_header_style header(&writer);
625     style_header_footer_properties hf(header.add_style_header_footer_properties());
626     hf.set_fo_min_height("20pt");
627     hf.set_fo_margin_left("0pt");
628     hf.set_fo_margin_right("0pt");
629     hf.set_fo_margin_bottom("10pt");
630     header.end();
631 
632     style_footer_style footer(&writer);
633     style_header_footer_properties hf2(footer.add_style_header_footer_properties());
634     hf2.set_fo_min_height("20pt");
635     hf2.set_fo_margin_left("0pt");
636     hf2.set_fo_margin_right("0pt");
637     hf2.set_fo_margin_top("10pt");
638     footer.end();
639 
640     QString pageLyt = QString::fromUtf8(buf.buffer(), buf.buffer().size());
641     buf.close();
642     buf.setData("", 0);
643 
644     pageLayoutStyle.addProperty("1header-footer-style", pageLyt, KoGenStyle::StyleChildElement);
645     pageLayoutStyleName = mainStyles->insert(pageLayoutStyle, pageLayoutStyleName, KoGenStyles::DontAddNumberToName);
646 
647     for (unsigned i = 0; i < workbook->sheetCount(); ++i) {
648         Sheet* sheet = workbook->sheet(i);
649         processSheetForStyle(sheet, xmlWriter);
650 
651         buf.open(QIODevice::WriteOnly);
652         processSheetForHeaderFooter(workbook->sheet(0), &writer);
653         contentElement = QString::fromUtf8(buf.buffer(), buf.buffer().size());
654         buf.close();
655         QString childElementName = QString::number(i).append("master-style");
656         KoGenStyle masterStyle(KoGenStyle::MasterPageStyle);
657         masterStyle.addChildElement(childElementName, contentElement);
658         masterStyle.addAttribute("style:page-layout-name", pageLayoutStyleName);
659 
660         masterStyleName = mainStyles->insert(masterStyle, masterStyleName, KoGenStyles::DontAddNumberToName);
661         masterStyle.addAttribute("style:name", masterStyleName);
662     }
663 }
664 
665 // Processes a sheet.
processSheetForBody(Sheet * sheet,KoXmlWriter * xmlWriter,office_spreadsheet & spreadsheet)666 void ExcelImport::Private::processSheetForBody(Sheet* sheet, KoXmlWriter* xmlWriter, office_spreadsheet& spreadsheet)
667 {
668     if (!sheet) return;
669     if (!xmlWriter) return;
670 
671     table_table table(spreadsheet.add_table_table());
672 
673     table.set_table_name(sheet->name());
674     table.set_table_print("false");
675     table.set_table_style_name(sheetStyles[sheetFormatIndex]);
676     ++sheetFormatIndex;
677 
678     if(sheet->password() != 0) {
679         //TODO
680        //xmlWriter->addAttribute("table:protected", "true");
681        //xmlWriter->addAttribute("table:protection-key", uint(sheet->password()));
682     }
683 
684     if (!sheet->drawObjects().isEmpty()) {
685         table_shapes shapes(table.add_table_shapes());
686         shapes.addCompleteElement(sheetShapes[sheet]);
687     }
688 
689 
690     const unsigned columnCount = qMin(maximalColumnCount, sheet->maxColumn());
691     unsigned outlineLevel = 0;
692     for (unsigned i = 0; i <= columnCount; ++i) {
693         processColumnForBody(sheet, i, table, outlineLevel);
694     }
695 
696     // in odf default-cell-style's only apply to cells/rows/columns that are present in the file while in Excel
697     // row/column styles should apply to all cells in that row/column. So, try to fake that behavior by writing
698     // a number-columns-repeated to apply the styles/formattings to "all" columns.
699     if (columnCount < maximalColumnCount-1) {
700         table_table_column column(table.add_table_table_column());
701         column.set_table_style_name(defaultColumnStyles[defaultColumnStyleIndex]);
702         column.set_table_number_columns_repeated(maximalColumnCount - 1 - columnCount);
703     }
704 
705     // add rows
706     outlineLevel = 0;
707     const unsigned rowCount = qMin(maximalRowCount, sheet->maxRow());
708     for (unsigned i = 0; i <= rowCount;) {
709         i += processRowForBody(sheet, i, xmlWriter, table, outlineLevel);
710     }
711 
712     // same we did above with columns is also needed for rows.
713     if(rowCount < maximalRowCount-1) {
714         table_table_row row(table.add_table_table_row());
715         row.set_table_number_rows_repeated(maximalRowCount - 1 - rowCount);
716         row.add_table_table_cell();
717     }
718     ++defaultColumnStyleIndex;
719 }
720 
getRect(const MSO::OfficeArtFSPGR & r)721 static QRectF getRect(const MSO::OfficeArtFSPGR &r)
722 {
723     return QRect(r.xLeft, r.yTop, r.xRight - r.xLeft, r.yBottom - r.yTop);
724 }
725 
726 // Processes styles for a sheet.
processSheetForStyle(Sheet * sheet,KoXmlWriter * xmlWriter)727 void ExcelImport::Private::processSheetForStyle(Sheet* sheet, KoXmlWriter* xmlWriter)
728 {
729     if (!sheet) return;
730     if (!xmlWriter) return;
731 
732     KoGenStyle style(KoGenStyle::TableAutoStyle, "table");
733     style.addAttribute("style:master-page-name", "Default");
734 
735     style.addProperty("table:display", sheet->visible() ? "true" : "false");
736     style.addProperty("table:writing-mode", "lr-tb");
737 
738     processSheetBackground(sheet, style);
739 
740     QString styleName = styles->insert(style, "ta");
741     sheetStyles.append(styleName);
742 
743     createDefaultColumnStyle( sheet );
744 
745     const unsigned columnCount = qMin(maximalColumnCount, sheet->maxColumn());
746     for (unsigned i = 0; i <= columnCount; ++i) {
747         processColumnForStyle(sheet, i, xmlWriter);
748     }
749 
750     const unsigned rowCount = qMin(maximalRowCount, sheet->maxRow());
751     for (unsigned i = 0; i <= rowCount;) {
752         i += processRowForStyle(sheet, i, xmlWriter);
753     }
754 
755     QList<OfficeArtObject*> objects = sheet->drawObjects();
756     int drawObjectGroups = sheet->drawObjectsGroupCount();
757     if (!objects.empty() || drawObjectGroups) {
758         ODrawClient client = ODrawClient(sheet);
759         ODrawToOdf odraw(client);
760         QBuffer b;
761         KoXmlWriter xml(&b);
762         Writer writer(xml, *styles, false);
763         foreach (const OfficeArtObject* o, objects) {
764             client.setShapeText(o->text());
765             odraw.processDrawingObject(o->object(), writer);
766         }
767         for (int i = 0; i < drawObjectGroups; ++i) {
768             xml.startElement("draw:g");
769 
770             const MSO::OfficeArtSpgrContainer& group = sheet->drawObjectsGroup(i);
771             const MSO::OfficeArtSpContainer* first = group.rgfb.first().anon.get<MSO::OfficeArtSpContainer>();
772             if (first && first->clientAnchor && first->shapeGroup) {
773                 QRectF oldCoords = client.getGlobalRect(*first->clientAnchor);
774                 QRectF newCoords = getRect(*first->shapeGroup);
775                 Writer transw = writer.transform(oldCoords, newCoords);
776                 foreach (const OfficeArtObject* o, sheet->drawObjects(i)) {
777                     client.setShapeText(o->text());
778                     odraw.processDrawingObject(o->object(), transw);
779                 }
780             } else {
781                 foreach (const OfficeArtObject* o, sheet->drawObjects(i)) {
782                     client.setShapeText(o->text());
783                     odraw.processDrawingObject(o->object(), writer);
784                 }
785             }
786             xml.endElement(); // draw:g
787         }
788         sheetShapes[sheet] = b.data();
789         //qCDebug(lcExcelImport) << b.data();
790     }
791 }
792 
793 // Processes headers and footers for a sheet.
processSheetForHeaderFooter(Sheet * sheet,KoXmlWriter * xmlWriter)794 void ExcelImport::Private::processSheetForHeaderFooter(Sheet* sheet, KoXmlWriter* xmlWriter)
795 {
796     if (!sheet) return;
797     if (!xmlWriter) return;
798 
799     style_header header(xmlWriter);
800     if (!sheet->leftHeader().isEmpty()) {
801         style_region_left left(header.add_style_region_left());
802         text_p p(left.add_text_p());
803         processHeaderFooterStyle(sheet->leftHeader(), p);
804     }
805     if (!sheet->centerHeader().isEmpty()) {
806         style_region_center center(header.add_style_region_center());
807         text_p p(center.add_text_p());
808         processHeaderFooterStyle(sheet->centerHeader(), p);
809     }
810     if (!sheet->rightHeader().isEmpty()) {
811         style_region_right right(header.add_style_region_right());
812         text_p p(right.add_text_p());
813         processHeaderFooterStyle(sheet->rightHeader(), p);
814     }
815     header.end();
816 
817     style_footer footer(xmlWriter);
818     if (!sheet->leftFooter().isEmpty()) {
819         style_region_left left(footer.add_style_region_left());
820         text_p p(left.add_text_p());
821         processHeaderFooterStyle(sheet->leftFooter(), p);
822     }
823     if (!sheet->centerFooter().isEmpty()) {
824         style_region_center center(footer.add_style_region_center());
825         text_p p(center.add_text_p());
826         processHeaderFooterStyle(sheet->centerFooter(), p);
827     }
828     if (!sheet->rightFooter().isEmpty()) {
829         style_region_right right(footer.add_style_region_right());
830         text_p p(right.add_text_p());
831         processHeaderFooterStyle(sheet->rightFooter(), p);
832     }
833 }
834 
835 // Processes the styles of a headers and footers for a sheet.
processHeaderFooterStyle(const QString & text,text_p & p)836 void ExcelImport::Private::processHeaderFooterStyle(const QString& text, text_p& p)
837 {
838     bool skipUnsupported = false;
839     int lastPos;
840     int pos = text.indexOf('&');
841     int len = text.length();
842     if ((pos < 0) && (text.length() > 0))   // If ther is no &
843         p.addTextNode(text);
844     else if (pos > 0) // Some text and '&'
845         p.addTextNode(text.mid(0,  pos - 1));
846 
847     while (pos >= 0) {
848         switch (text[pos + 1].unicode()) {
849         case 'D':
850             p.add_text_date().addTextNode(QDate::currentDate().toString("DD/MM/YYYY"));
851             break;
852         case 'T':
853             p.add_text_time().addTextNode(QTime::currentTime().toString("HH:MM:SS"));
854             break;
855         case 'P':
856             p.add_text_page_number().addTextNode("1");
857             break;
858         case 'N':
859             p.add_text_page_count().addTextNode("999");
860             break;
861         case 'F':
862             p.add_text_title().addTextNode("???");
863             break;
864         case 'A':
865             p.add_text_sheet_name().addTextNode("???");
866             break;
867         case '\"':
868         default:
869             skipUnsupported = true;
870             break;
871         }
872         lastPos = pos;
873         pos = text.indexOf('&', lastPos + 1);
874         if (!skipUnsupported && (pos > (lastPos + 1)))
875             p.addTextNode(text.mid(lastPos + 2, (pos - lastPos - 2)));
876         else if (!skipUnsupported && (pos < 0))  //Remaining text
877             p.addTextNode(text.mid(lastPos + 2, len - (lastPos + 2)));
878         else
879             skipUnsupported = false;
880     }
881 }
882 
883 // Processes a column in a sheet.
processColumnForBody(Sheet * sheet,int columnIndex,group_table_columns_and_groups & table,unsigned & outlineLevel)884 void ExcelImport::Private::processColumnForBody(Sheet* sheet, int columnIndex, group_table_columns_and_groups& table, unsigned& outlineLevel)
885 {
886     Column* column = sheet->column(columnIndex, false);
887 
888     unsigned newOutlineLevel = column ? column->outlineLevel() : 0;
889     if (newOutlineLevel > outlineLevel) {
890         table_table_column_group group(table.add_table_table_column_group());
891         outlineLevel++;
892         if (outlineLevel == newOutlineLevel && column->collapsed())
893             group.set_table_display("false");
894         processColumnForBody(sheet, columnIndex, group, outlineLevel);
895         outlineLevel--;
896         return;
897     }
898 
899     if (!column) {
900         table_table_column column(table.add_table_table_column());
901         Q_ASSERT(defaultColumnStyleIndex < defaultColumnStyles.count());
902         column.set_table_style_name(defaultColumnStyles[defaultColumnStyleIndex] );
903         return;
904     }
905     Q_ASSERT(columnFormatIndex < colStyles.count());
906     Q_ASSERT(columnFormatIndex < colCellStyles.count());
907     const QString styleName = colStyles[columnFormatIndex];
908     const QString defaultStyleName = colCellStyles[columnFormatIndex];
909     columnFormatIndex++;
910 
911     table_table_column c(table.add_table_table_column());
912     c.set_table_default_cell_style_name(defaultStyleName);
913     c.set_table_visibility(column->visible() ? "visible" : "collapse");
914     //c.set_table_number_columns_repeated( );
915     c.set_table_style_name(styleName);
916 }
917 
918 // Processes the style of a column in a sheet.
processColumnForStyle(Sheet * sheet,int columnIndex,KoXmlWriter * xmlWriter)919 void ExcelImport::Private::processColumnForStyle(Sheet* sheet, int columnIndex, KoXmlWriter* xmlWriter)
920 {
921     Column* column = sheet->column(columnIndex, false);
922 
923     if (!xmlWriter) return;
924     if (!column) return;
925 
926     KoGenStyle style(KoGenStyle::TableColumnAutoStyle, "table-column");
927     style.addProperty("fo:break-before", "auto");
928     style.addPropertyPt("style:column-width", column->width());
929 
930     QString styleName = styles->insert(style, "co");
931     colStyles.append(styleName);
932 
933     const Format* format = &column->format();
934     QString cellStyleName = processCellFormat(format);
935     colCellStyles.append(cellStyleName);
936 }
937 
938 // Processes a row in a sheet.
processRowForBody(Sheet * sheet,int rowIndex,KoXmlWriter * xmlWriter,group_table_rows_and_groups & table,unsigned & outlineLevel)939 int ExcelImport::Private::processRowForBody(Sheet* sheet, int rowIndex, KoXmlWriter* xmlWriter, group_table_rows_and_groups& table, unsigned& outlineLevel)
940 {
941     int repeat = 1;
942 
943     Row *row = sheet->row(rowIndex, false);
944 
945     unsigned newOutlineLevel = row ? row->outlineLevel() : 0;
946     if (newOutlineLevel > outlineLevel) {
947         table_table_row_group group(table.add_table_table_row_group());
948         outlineLevel++;
949         if (outlineLevel == newOutlineLevel && row->collapsed())
950             group.set_table_display("false");
951         processRowForBody(sheet, rowIndex, xmlWriter, group, outlineLevel);
952         outlineLevel--;
953         return repeat;
954     }
955 
956     if (!row) {
957         table_table_row row(table.add_table_table_row());
958         row.add_table_table_cell();
959         return repeat;
960     }
961     if (!row->sheet()) return repeat;
962 
963     const QString styleName = rowStyles[rowFormatIndex];
964     rowFormatIndex++;
965 
966     repeat = rowsRepeated(row, rowIndex);
967 
968     table_table_row r(table.add_table_table_row());
969     r.set_table_visibility(row->visible() ? "visible" : "collapse");
970     r.set_table_style_name(styleName);
971 
972     if(repeat > 1)
973         r.set_table_number_rows_repeated(repeat);
974 
975     // find the column of the rightmost cell (if any)
976     const int lastCol = row->sheet()->maxCellsInRow(rowIndex);
977     int i = 0;
978     do {
979         Cell* cell = row->sheet()->cell(i, row->index(), false);
980         if (cell) {
981             processCellForBody(cell, xmlWriter, r);
982             i += cell->columnRepeat();
983         } else { // empty cell
984             r.add_table_table_cell();
985             ++i;
986         }
987     } while(i <= lastCol);
988 
989     addProgress(repeat);
990     return repeat;
991 }
992 
993 // Processes the style of a row in a sheet.
processRowForStyle(Sheet * sheet,int rowIndex,KoXmlWriter * xmlWriter)994 int ExcelImport::Private::processRowForStyle(Sheet* sheet, int rowIndex, KoXmlWriter* xmlWriter)
995 {
996     int repeat = 1;
997     Row* row = sheet->row(rowIndex, false);
998 
999     if (!row) return repeat;
1000     if (!row->sheet()) return repeat;
1001     if (!xmlWriter) return repeat;
1002 
1003     repeat = rowsRepeated(row, rowIndex);
1004 
1005     Format format = row->format();
1006     QString cellStyleName = processRowFormat(&format, "auto", repeat, row->height());
1007     rowStyles.append(cellStyleName);
1008 
1009     const int lastCol = row->sheet()->maxCellsInRow(rowIndex);
1010     for (int i = 0; i <= lastCol;) {
1011         Cell* cell = row->sheet()->cell(i, row->index(), false);
1012         if (cell) {
1013             processCellForStyle(cell, xmlWriter);
1014             i += cell->columnRepeat();
1015         } else { // row has no style
1016             ++i;
1017         }
1018     }
1019 
1020     addProgress(repeat);
1021     return repeat;
1022 }
1023 
1024 // Another form of conditional formats are those that define a different format
1025 // depending on the value. In following examples the different states are
1026 // splittet with a ; char, the first is usually used if the value is positive,
1027 // the second if the value if negavtive, the third if the value is invalid and
1028 // the forth defines a common formatting mask.
1029 // _("$"* #,##0.0000_);_("$"* \(#,##0.0000\);_("$"* "-"????_);_(@_)
1030 // _-[$₩-412]* #,##0.0000_-;\-[$₩-412]* #,##0.0000_-;_-[$₩-412]* "-"????_-;_-@_-
1031 // _ * #,##0_)[$€-1]_ ;_ * #,##0[$€-1]_ ;_ * "-"_)[$€-1]_ ;_ @_ "
extractConditional(const QString & _text)1032 QString extractConditional(const QString &_text)
1033 {
1034     const QString text = removeEscaped(_text);
1035 #if 1
1036     if (text.startsWith('_') && text.length() >= 3) {
1037         QChar end;
1038         if (text[1] == '(') end = ')';
1039         else if (text[1] == '_') end = '_';
1040         else if (text[1] == ' ') end = ' ';
1041         else qCDebug(lcExcelImport) << "Probably unhandled condition=" << text[1] << "in text=" << text;
1042         if (! end.isNull()) {
1043             {
1044                 QString regex = QString("^_%1(.*\"\\$\".*)%2;.*").arg(QString("\\%1").arg(text[1])).arg(QString("\\%1").arg(end));
1045                 QRegExp ex(regex);
1046                 ex.setMinimal(true);
1047                 if (ex.indexIn(text) >= 0) return ex.cap(1);
1048             }
1049             {
1050                 QString regex = QString("^_%1(.*\\[\\$.*\\].*)%2;.*").arg(QString("\\%1").arg(text[1])).arg(QString("\\%1").arg(end));
1051                 QRegExp ex(regex);
1052                 ex.setMinimal(true);
1053                 if (ex.indexIn(text) >= 0) return ex.cap(1);
1054             }
1055         }
1056     }
1057 #else
1058     if (text.startsWith('_')) {
1059         return text.split(';').first();
1060     }
1061 #endif
1062     return text;
1063 }
1064 
1065 #if 0
1066 // Currency or accounting format.
1067 // "$"* #,##0.0000_
1068 // [$EUR]\ #,##0.00"
1069 // [$₩-412]* #,##0.0000
1070 // * #,##0_)[$€-1]_
1071 static bool currencyFormat(const QString& valueFormat, QString *currencyVal = 0, QString * formatVal = 0)
1072 {
1073     QString vf = extractConditional(valueFormat);
1074 
1075     // dollar is special cause it starts with a $
1076     QRegExp dollarRegEx("^\"\\$\"[*\\-\\s]*([#,]*[\\d]+(|.[#0]+)).*");
1077     if (dollarRegEx.indexIn(vf) >= 0) {
1078         if (currencyVal) *currencyVal = "$";
1079         if (formatVal) *formatVal = dollarRegEx.cap(1);
1080         return true;
1081     }
1082 
1083     // every other currency or accounting has a [$...] identifier
1084     QRegExp crRegEx("\\[\\$(.*)\\]");
1085     crRegEx.setMinimal(true);
1086     if (crRegEx.indexIn(vf) >= 0) {
1087         if (currencyVal) {
1088             *currencyVal = crRegEx.cap(1);
1089         }
1090         if (formatVal) {
1091             QRegExp vlRegEx("([#,]*[\\d]+(|.[#0]+))");
1092             *formatVal = vlRegEx.indexIn(vf) >= 0 ? vlRegEx.cap(1) : QString();
1093         }
1094         return true;
1095     }
1096 
1097     return false;
1098 }
1099 #endif
1100 
1101 // Checks if the as argument passed formatstring defines a date-format or not.
isDateFormat(const QString & valueFormat)1102 bool ExcelImport::Private::isDateFormat(const QString& valueFormat)
1103 {
1104     KoGenStyle& style = valueFormatCache[valueFormat];
1105     if (style.isEmpty()) {
1106         style = NumberFormatParser::parse( valueFormat );
1107     }
1108     return style.type() == KoGenStyle::NumericDateStyle;
1109 }
1110 
1111 #if 0
1112 static QByteArray convertCurrency(double currency, const QString& valueFormat)
1113 {
1114     Q_UNUSED(valueFormat);
1115     return QByteArray::number(currency, 'g', 15);
1116 }
1117 #endif
1118 
convertDate(double serialNo,const QString & valueFormat)1119 static QString convertDate(double serialNo, const QString& valueFormat)
1120 {
1121     QString vf = valueFormat;
1122     QString locale = extractLocale(vf);
1123     Q_UNUSED(locale);   //TODO http://msdn.microsoft.com/en-us/goglobal/bb964664.aspx
1124     Q_UNUSED(vf);   //TODO
1125 
1126     // reference is midnight 30 Dec 1899
1127     QDateTime dt(QDate(1899, 12, 30));
1128     dt = dt.addMSecs((qint64)(serialNo * 86400 * 1000)); // TODO: we probably need double precision here
1129 
1130     //TODO atm we always return a datetime. This works great (time ignored if only date was defined) with Calligra Sheets but probably not with other customers...
1131     //return dd.toString("yyyy-MM-dd");
1132     return dt.toString("yyyy-MM-ddThh:mm:ss");
1133 }
1134 
convertToTime(double serialNo)1135 static QTime convertToTime(double serialNo)
1136 {
1137     //QString locale = extractLocale(vf);
1138     //Q_UNUSED(locale);   //TODO http://msdn.microsoft.com/en-us/goglobal/bb964664.aspx
1139 
1140     // reference is midnight 30 Dec 1899
1141     QTime tt;
1142     tt = tt.addMSecs(qRound((serialNo - (int)serialNo) * 86400 * 1000));
1143     qCDebug(lcExcelImport) << tt;
1144     return tt;
1145 }
1146 
cellFormula(Cell * cell)1147 QString cellFormula(Cell* cell)
1148 {
1149     QString formula = cell->formula();
1150     if (!formula.isEmpty()) {
1151         if(formula.startsWith("ROUNDUP(") || formula.startsWith("ROUNDDOWN(") || formula.startsWith("ROUND(") || formula.startsWith("RAND(")) {
1152             // Special case Excel formulas that differ from OpenFormula
1153             formula.prepend("msoxl:=");
1154         } else if (!formula.isEmpty()) {
1155             formula.prepend("=");
1156         }
1157     }
1158     return formula;
1159 }
1160 
currencyValue(const QString & value)1161 QString currencyValue(const QString &value)
1162 {
1163     if (value.isEmpty()) return QString();
1164     if (value[0] == '$') return "USD";
1165     if (value[0] == QChar(UNICODE_EUR)) return "EUR";
1166     if (value[0] == QChar(UNICODE_GBP)) return "GBP";
1167     if (value[0] == QChar(UNICODE_JPY)) return "JPY";
1168     QRegExp symbolRegEx("^([^a-zA-Z0-9\\-_\\s]+)");
1169     if (symbolRegEx.indexIn(value) >= 0) return symbolRegEx.cap(1);
1170     return QString();
1171 }
1172 
1173 // Processes a cell within a sheet.
processCellForBody(Cell * cell,KoXmlWriter * xmlWriter,table_table_row & row)1174 void ExcelImport::Private::processCellForBody(Cell* cell, KoXmlWriter* xmlWriter, table_table_row& row)
1175 {
1176     CellValue cellValue;
1177     if (cell->isCovered()) {
1178         table_covered_table_cell c(row.add_table_covered_table_cell());
1179         processCellAttributesForBody(cell, c, cellValue);
1180         processCellContentForBody(cell, xmlWriter, c, cellValue);
1181     } else {
1182         table_table_cell c(row.add_table_table_cell());
1183         if (cell->columnSpan() > 1)
1184             c.set_table_number_columns_spanned(cell->columnSpan());
1185         if (cell->rowSpan() > 1)
1186             c.set_table_number_rows_spanned(cell->rowSpan());
1187         processCellAttributesForBody(cell, c, cellValue);
1188         processCellContentForBody(cell, xmlWriter, c, cellValue);
1189     }
1190 }
1191 
processCellAttributesForBody(Cell * cell,group_table_table_cell_attlist & c,CellValue & cellValue)1192 void ExcelImport::Private::processCellAttributesForBody(Cell* cell, group_table_table_cell_attlist& c, CellValue& cellValue)
1193 {
1194     Q_ASSERT(cellFormatIndex >= 0 && cellFormatIndex < cellStyles.count());
1195     c.set_table_style_name(cellStyles[cellFormatIndex]);
1196     cellFormatIndex++;
1197 
1198     if (cell->columnRepeat() > 1)
1199         c.set_table_number_columns_repeated(cell->columnRepeat());
1200 
1201     const QString formula = cellFormula(cell);
1202     if (!formula.isEmpty())
1203         c.set_table_formula(formula);
1204 
1205     cellValue.value = cell->value();
1206     const Value& value = cellValue.value;
1207 
1208     if (value.isBoolean()) {
1209         c.set_office_value_type("boolean");
1210         c.set_office_boolean_value(value.asBoolean() ? "true" : "false");
1211     } else if (value.isFloat() || value.isInteger()) {
1212         const QString valueFormat = cell->format().valueFormat();
1213 
1214         if (isPercentageFormat(valueFormat)) {
1215             c.set_office_value_type("percentage");
1216             c.set_office_value(value.asFloat());
1217         } else if (isDateFormat(valueFormat)) {
1218             const QString dateValue = convertDate(value.asFloat(), valueFormat);
1219             c.set_office_value_type("date");
1220             c.set_office_date_value(dateValue);
1221         } else if (value.asFloat() < 1.0 && isTimeFormat(valueFormat)) {
1222             c.set_office_value_type("time");
1223             c.set_office_time_value(Duration(convertToTime(value.asInteger())));
1224         } else if (isFractionFormat(valueFormat)) {
1225             c.set_office_value_type("float");
1226             c.set_office_value(value.asFloat());
1227         } else { // fallback is the generic float format
1228             c.set_office_value_type("float");
1229             c.set_office_value(value.asFloat());
1230         }
1231     } else if (value.isText() || value.isError()) {
1232         cellValue.str = value.asString();
1233 
1234         cellValue.link = cell->hyperlink();
1235         if (cellValue.link.isValid) {
1236             cellValue.linkLocation = cellValue.link.location;
1237             if(!cellValue.linkLocation.isEmpty()) {
1238                 cellValue.linkName = cellValue.link.displayName.trimmed();
1239                 if(cellValue.linkName.isEmpty())
1240                     cellValue.linkName = cellValue.str;
1241                 cellValue.str.clear(); // at Excel cells with links don't have additional text content
1242             }
1243         }
1244         if (cellValue.linkLocation.isEmpty() && value.isString()) {
1245             c.set_office_value_type("string");
1246             if (!(cell->format().font().subscript() || cell->format().font().superscript()))
1247                 c.set_office_string_value(cellValue.str);
1248         }
1249     }
1250 }
1251 
processCellText(Cell * cell,group_paragraph_content & content,CellValue & cellValue)1252 void ExcelImport::Private::processCellText(Cell* cell, group_paragraph_content& content, CellValue& cellValue)
1253 {
1254     const QString& str = cellValue.str;
1255     if (cellValue.value.isString()) {
1256         content.addTextNode(str);
1257     } else {
1258         // rich text
1259         std::map<unsigned, FormatFont> formatRuns = cellValue.value.formatRuns();
1260 
1261         // add sentinel to list of format runs
1262         formatRuns[str.length()] = cell->format().font();
1263 
1264         unsigned index = 0;
1265         QString style;
1266         for (std::map<unsigned, FormatFont>::iterator it = formatRuns.begin(); it != formatRuns.end(); ++it) {
1267             if (it->first > index) {
1268                 if (!style.isEmpty()) {
1269                     text_span span(content.add_text_span());
1270                     span.set_text_style_name(style);
1271                     span.addTextNode(str.mid(index, it->first - index));
1272                 } else {
1273                     content.addTextNode(str.mid(index, it->first - index));
1274                 }
1275             }
1276 
1277             index = it->first;
1278 
1279             if (it->second == cell->format().font())
1280                 style.clear();
1281             else {
1282                 style = fontStyles.value(it->second);
1283             }
1284         }
1285     }
1286 }
1287 
processCellContentForBody(Cell * cell,KoXmlWriter * xmlWriter,group_table_table_cell_content & c,CellValue & cellValue)1288 void ExcelImport::Private::processCellContentForBody(Cell* cell,
1289         KoXmlWriter* xmlWriter, group_table_table_cell_content& c,
1290         CellValue& cellValue)
1291 {
1292     if (cellValue.value.isText() || cellValue.value.isError()) {
1293         text_p p(c.add_text_p());
1294 
1295         if(!cellValue.str.isEmpty()) {
1296             if (cell->format().font().subscript() || cell->format().font().superscript()) {
1297                 text_span span(p.add_text_span());
1298                 if (cell->format().font().subscript()) {
1299                     span.set_text_style_name(subScriptStyle);
1300                 } else {
1301                     span.set_text_style_name(superScriptStyle);
1302                 }
1303                 processCellText(cell, span, cellValue);
1304             } else {
1305                 processCellText(cell, p, cellValue);
1306             }
1307         }
1308 
1309         if (!cellValue.linkName.isEmpty()) {
1310             text_a a(p.add_text_a(urlFromArg(cellValue.linkLocation)));
1311             const QString targetFrameName = cellValue.link.targetFrameName;
1312             if (! targetFrameName.isEmpty())
1313                 a.set_office_target_frame_name(targetFrameName);
1314             a.addTextNode(cellValue.linkName);
1315         }
1316     }
1317 
1318     const QString note = cell->note();
1319     if (! note.isEmpty()) {
1320         office_annotation annotation(c.add_office_annotation());
1321         //dc_creator creator(annotation.add_dc_creator());
1322         //creator.addTextNode(authorName); //TODO
1323         text_p p(annotation.add_text_p());
1324         p.addTextNode(note);
1325     }
1326 
1327 
1328     // handle charts
1329     foreach(ChartObject *chart, cell->charts()) {
1330         Sheet* const sheet = cell->sheet();
1331         if(chart->m_chart->m_impl==0) {
1332             qCDebug(lcExcelImport) << "Invalid chart to be created, no implementation.";
1333             continue;
1334         }
1335 
1336         KoOdfChartWriter *c = new KoOdfChartWriter(chart->m_chart);
1337         c->m_href = QString("Chart%1").arg(this->charts.count()+1);
1338         c->m_endCellAddress = encodeAddress(sheet->name(), chart->m_colR, chart->m_rwB);
1339         c->m_notifyOnUpdateOfRanges = "Sheet1.D2:Sheet1.F2";
1340 
1341         const unsigned long colL = chart->m_colL;
1342         const unsigned long dxL = chart->m_dxL;
1343         //const unsigned long colR = chart->m_colR;
1344         //const unsigned long dxR = chart->m_dxR;
1345         //const unsigned long rwB = chart->m_rwB;
1346         const unsigned long dyT = chart->m_dyT;
1347         const unsigned long rwT = chart->m_rwT;
1348         //const unsigned long dyB = chart->m_dyB;
1349 
1350         c->m_x = offset(columnWidth(sheet, colL), dxL, 1024);
1351         c->m_y = offset(rowHeight(sheet, rwT), dyT, 256);
1352 
1353         if (!chart->m_chart->m_cellRangeAddress.isNull() )
1354             c->m_cellRangeAddress = encodeAddress(sheet->name(), chart->m_chart->m_cellRangeAddress.left(), chart->m_chart->m_cellRangeAddress.top()) + ":" +
1355                                     encodeAddress(sheet->name(), chart->m_chart->m_cellRangeAddress.right(), chart->m_chart->m_cellRangeAddress.bottom());
1356 
1357         this->charts << c;
1358 
1359         c->saveIndex(xmlWriter);
1360     }
1361 
1362     // handle graphics objects
1363     if (!cell->drawObjects().isEmpty()) {
1364         xmlWriter->addCompleteElement(cellShapes[cell].data());
1365     }
1366 }
1367 
processCharts(KoXmlWriter * manifestWriter)1368 void ExcelImport::Private::processCharts(KoXmlWriter* manifestWriter)
1369 {
1370     foreach(KoOdfChartWriter *c, this->charts) {
1371         c->saveContent(this->storeout, manifestWriter);
1372     }
1373 }
1374 
1375 // Processes style for a cell within a sheet.
processCellForStyle(Cell * cell,KoXmlWriter * xmlWriter)1376 void ExcelImport::Private::processCellForStyle(Cell* cell, KoXmlWriter* xmlWriter)
1377 {
1378     if (!cell) return;
1379     if (!xmlWriter) return;
1380 
1381     // TODO optimize with hash table
1382     const Format* format = &cell->format();
1383     QString styleName = processCellFormat(format, cellFormula(cell));
1384     cellStyles.append(styleName);
1385 
1386     if (cell->value().isRichText()) {
1387         std::map<unsigned, FormatFont> formatRuns = cell->value().formatRuns();
1388         for (std::map<unsigned, FormatFont>::iterator it = formatRuns.begin(); it != formatRuns.end(); ++it) {
1389             if (fontStyles.contains(it->second)) continue;
1390             KoGenStyle style(KoGenStyle::TextAutoStyle, "text");
1391             processFontFormat(it->second, style, true);
1392             QString styleName = styles->insert(style, "T");
1393             fontStyles[it->second] = styleName;
1394         }
1395     }
1396 
1397     if (format->font().superscript() && superScriptStyle.isEmpty()) {
1398         KoGenStyle style(KoGenStyle::TextAutoStyle, "text");
1399         style.addProperty("style:text-position", "super", KoGenStyle::TextType);
1400         superScriptStyle = styles->insert(style, "T");
1401     }
1402     if (format->font().subscript() && subScriptStyle.isEmpty()) {
1403         KoGenStyle style(KoGenStyle::TextAutoStyle, "text");
1404         style.addProperty("style:text-position", "sub", KoGenStyle::TextType);
1405         subScriptStyle = styles->insert(style, "T");
1406     }
1407 
1408     QList<OfficeArtObject*> objects = cell->drawObjects();
1409     if (!objects.empty()) {
1410         ODrawClient client = ODrawClient(cell->sheet());
1411         ODrawToOdf odraw( client);
1412         QBuffer b;
1413         KoXmlWriter xml(&b);
1414         Writer writer(xml, *styles, false);
1415         foreach (OfficeArtObject* o, objects) {
1416             client.setShapeText(o->text());
1417             odraw.processDrawingObject(o->object(), writer);
1418         }
1419         cellShapes[cell] = b.data();
1420         //qCDebug(lcExcelImport) << cell->columnLabel() << cell->row() << b.data();
1421     }
1422 }
1423 
1424 
1425 // Processes styles for a cell within a sheet.
processCellFormat(const Format * format,const QString & formula)1426 QString ExcelImport::Private::processCellFormat(const Format* format, const QString& formula)
1427 {
1428     CellFormatKey key(format, formula);
1429     QString& styleName = cellFormatCache[key];
1430     if (styleName.isEmpty()) {
1431         // handle data format, e.g. number style
1432         QString refName;
1433         if (!key.isGeneral) {
1434             refName = processValueFormat(format->valueFormat());
1435         } else {
1436             if (key.decimalCount >= 0) {
1437                 KoGenStyle style(KoGenStyle::NumericNumberStyle);
1438                 QBuffer buffer;
1439                 buffer.open(QIODevice::WriteOnly);
1440                 KoXmlWriter xmlWriter(&buffer);    // TODO pass indentation level
1441                 number_number number(&xmlWriter);
1442                 number.set_number_decimal_places(key.decimalCount);
1443                 number.end();
1444                 QString elementContents = QString::fromUtf8(buffer.buffer(), buffer.buffer().size());
1445                 style.addChildElement("number", elementContents);
1446                 refName = styles->insert(style, "N");
1447             }
1448         }
1449 
1450         KoGenStyle style(KoGenStyle::TableCellAutoStyle, "table-cell");
1451         // now the real table-cell
1452         if (!refName.isEmpty())
1453             style.addAttribute("style:data-style-name", refName);
1454 
1455         processFormat(format, style);
1456         styleName = styles->insert(style, "ce");
1457     }
1458     return styleName;
1459 }
1460 
1461 // Processes styles for a row within a sheet.
processRowFormat(Format * format,const QString & breakBefore,int rowRepeat,double rowHeight)1462 QString ExcelImport::Private::processRowFormat(Format* format, const QString& breakBefore, int rowRepeat, double rowHeight)
1463 {
1464     QString refName;
1465     QString valueFormat = format->valueFormat();
1466     if (valueFormat != QString("General"))
1467         refName = processValueFormat(valueFormat);
1468 
1469     KoGenStyle style(KoGenStyle::TableRowAutoStyle, "table-row");
1470     // now the real table-cell
1471     if (!refName.isEmpty())
1472         style.addAttribute("style:data-style-name", refName);
1473     // set break-before
1474     if(!breakBefore.isEmpty())
1475         style.addProperty("fo:break-before", breakBefore);
1476     // set how often the row should be repeated
1477     if (rowRepeat > 1)
1478         style.addAttribute("table:number-rows-repeated", rowRepeat);
1479     // set the height of the row
1480     if (rowHeight >= 0)
1481         style.addPropertyPt("style:row-height", rowHeight);
1482 
1483     processFormat(format, style);
1484     QString styleName = styles->insert(style, "ro");
1485     return styleName;
1486 }
1487 
convertColor(const QColor & color)1488 QString convertColor(const QColor& color)
1489 {
1490     char buf[8];
1491     sprintf(buf, "#%02x%02x%02x", color.red(), color.green(), color.blue());
1492     return QString(buf);
1493 }
1494 
convertBorder(const QString & which,const QString & lineWidthProperty,const Pen & pen,KoGenStyle & style)1495 void convertBorder(const QString& which, const QString& lineWidthProperty, const Pen& pen, KoGenStyle& style)
1496 {
1497     if (pen.style == Pen::NoLine || pen.width == 0) {
1498         //style.addProperty(which, "none");
1499     } else {
1500         QString result;
1501         if (pen.style == Pen::DoubleLine) {
1502             result += QString::number(pen.width * 3);
1503         } else {
1504             result = QString::number(pen.width);
1505         }
1506         result += "pt ";
1507 
1508         switch (pen.style) {
1509         case Pen::SolidLine: result += "solid "; break;
1510         case Pen::DashLine: result += "dashed "; break;
1511         case Pen::DotLine: result += "dotted "; break;
1512         case Pen::DashDotLine: result += "dot-dash "; break;
1513         case Pen::DashDotDotLine: result += "dot-dot-dash "; break;
1514         case Pen::DoubleLine: result += "double "; break;
1515         }
1516 
1517         result += convertColor(pen.color);
1518 
1519         style.addProperty(which, result);
1520         if (pen.style == Pen::DoubleLine) {
1521             result = QString::number(pen.width);
1522             result = result + "pt " + result + "pt " + result + "pt";
1523             style.addProperty(lineWidthProperty, result);
1524         }
1525     }
1526 }
1527 
processFontFormat(const FormatFont & font,KoGenStyle & style,bool allProps)1528 void ExcelImport::Private::processFontFormat(const FormatFont& font, KoGenStyle& style, bool allProps)
1529 {
1530     if (font.isNull()) return;
1531 
1532     if (font.bold()) {
1533         style.addProperty("fo:font-weight", "bold", KoGenStyle::TextType);
1534     } else if (allProps) {
1535         style.addProperty("fo:font-weight", "normal", KoGenStyle::TextType);
1536     }
1537 
1538     if (font.italic()) {
1539         style.addProperty("fo:font-style", "italic", KoGenStyle::TextType);
1540     } else if (allProps) {
1541         style.addProperty("fo:font-style", "normal", KoGenStyle::TextType);
1542     }
1543 
1544     if (font.underline()) {
1545         style.addProperty("style:text-underline-type", "single", KoGenStyle::TextType);
1546         style.addProperty("style:text-underline-style", "solid", KoGenStyle::TextType);
1547         style.addProperty("style:text-underline-width", "auto", KoGenStyle::TextType);
1548         style.addProperty("style:text-underline-color", "font-color", KoGenStyle::TextType);
1549     } else if (allProps) {
1550         style.addProperty("style:text-underline-type", "none", KoGenStyle::TextType);
1551         style.addProperty("style:text-underline-style", "none", KoGenStyle::TextType);
1552     }
1553 
1554     if (font.strikeout()) {
1555         style.addProperty("style:text-line-through-type", "single", KoGenStyle::TextType);
1556         style.addProperty("style:text-line-through-style", "solid", KoGenStyle::TextType);
1557     } else {
1558         style.addProperty("style:text-line-through-type", "none", KoGenStyle::TextType);
1559         style.addProperty("style:text-line-through-style", "none", KoGenStyle::TextType);
1560     }
1561 
1562     if (!font.fontFamily().isEmpty())
1563         style.addProperty("fo:font-family", font.fontFamily(), KoGenStyle::TextType);
1564 
1565     style.addPropertyPt("fo:font-size", font.fontSize(), KoGenStyle::TextType);
1566 
1567     style.addProperty("fo:color", convertColor(font.color()), KoGenStyle::TextType);
1568 }
1569 
1570 // Processes a formatting.
processFormat(const Format * format,KoGenStyle & style)1571 void ExcelImport::Private::processFormat(const Format* format, KoGenStyle& style)
1572 {
1573     if (!format) return;
1574 
1575     FormatFont font = format->font();
1576     FormatAlignment align = format->alignment();
1577     FormatBackground back = format->background();
1578     FormatBorders borders = format->borders();
1579 
1580     processFontFormat(font, style);
1581 
1582     if (!align.isNull()) {
1583         switch (align.alignY()) {
1584         case Format::Top:
1585             style.addProperty("style:vertical-align", "top");
1586             break;
1587         case Format::Middle:
1588             style.addProperty("style:vertical-align", "middle");
1589             break;
1590         case Format::Bottom:
1591             style.addProperty("style:vertical-align", "bottom");
1592             break;
1593         case Format::VJustify:
1594             style.addProperty("style:vertical-align", "top");
1595             style.addProperty("calligra:vertical-distributed", "distributed");
1596             break;
1597         case Format::VDistributed:
1598             style.addProperty("style:vertical-align", "middle");
1599             style.addProperty("calligra:vertical-distributed", "distributed");
1600             break;
1601         }
1602 
1603         style.addProperty("fo:wrap-option", align.wrap() ? "wrap" : "no-wrap");
1604 
1605         if (align.rotationAngle()) {
1606             style.addProperty("style:rotation-angle", QString::number(align.rotationAngle()));
1607         }
1608 
1609         if (align.stackedLetters()) {
1610             style.addProperty("style:direction", "ttb");
1611         }
1612 
1613         if (align.shrinkToFit()) {
1614             style.addProperty("style:shrink-to-fit", "true");
1615         }
1616     }
1617 
1618     if (!borders.isNull()) {
1619         convertBorder("fo:border-left", "fo:border-line-width-left", borders.leftBorder(), style);
1620         convertBorder("fo:border-right", "fo:border-line-width-right", borders.rightBorder(), style);
1621         convertBorder("fo:border-top", "fo:border-line-width-top", borders.topBorder(), style);
1622         convertBorder("fo:border-bottom", "fo:border-line-width-bottom", borders.bottomBorder(), style);
1623         convertBorder("style:diagonal-tl-br", "style:diagonal-tl-br-widths", borders.topLeftBorder(), style);
1624         convertBorder("style:diagonal-bl-tr", "style:diagonal-bl-tr-widths", borders.bottomLeftBorder(), style);
1625     }
1626 
1627     if (!back.isNull() && back.pattern() != FormatBackground::EmptyPattern) {
1628         KoGenStyle fillStyle = KoGenStyle(KoGenStyle::GraphicAutoStyle, "graphic");
1629 
1630         QColor backColor = back.backgroundColor();
1631         if (back.pattern() == FormatBackground::SolidPattern)
1632             backColor = back.foregroundColor();
1633         const QString bgColor = convertColor(backColor);
1634         style.addProperty("fo:background-color", bgColor);
1635         switch(back.pattern()) {
1636             case FormatBackground::SolidPattern:
1637                 fillStyle.addProperty("draw:fill-color", bgColor);
1638                 fillStyle.addProperty("draw:transparency", "0%");
1639                 fillStyle.addProperty("draw:fill", "solid");
1640                 break;
1641             case FormatBackground::Dense3Pattern: // 88% gray
1642                 fillStyle.addProperty("draw:fill-color", "#000000");
1643                 fillStyle.addProperty("draw:transparency", "88%");
1644                 fillStyle.addProperty("draw:fill", "solid");
1645                 break;
1646             case FormatBackground::Dense4Pattern: // 50% gray
1647                 fillStyle.addProperty("draw:fill-color", "#000000");
1648                 fillStyle.addProperty("draw:transparency", "50%");
1649                 fillStyle.addProperty("draw:fill", "solid");
1650                 break;
1651             case FormatBackground::Dense5Pattern: // 37% gray
1652                 fillStyle.addProperty("draw:fill-color", "#000000");
1653                 fillStyle.addProperty("draw:transparency", "37%");
1654                 fillStyle.addProperty("draw:fill", "solid");
1655                 break;
1656             case FormatBackground::Dense6Pattern: // 12% gray
1657                 fillStyle.addProperty("draw:fill-color", "#000000");
1658                 fillStyle.addProperty("draw:transparency", "12%");
1659                 fillStyle.addProperty("draw:fill", "solid");
1660                 break;
1661             case FormatBackground::Dense7Pattern: // 6% gray
1662                 fillStyle.addProperty("draw:fill-color", "#000000");
1663                 fillStyle.addProperty("draw:transparency", "6%");
1664                 fillStyle.addProperty("draw:fill", "solid");
1665                 break;
1666             case FormatBackground::Dense1Pattern: // diagonal crosshatch
1667             case FormatBackground::Dense2Pattern: // thick diagonal crosshatch
1668             case FormatBackground::HorPattern: // Horizonatal lines
1669             case FormatBackground::VerPattern: // Vertical lines
1670             case FormatBackground::BDiagPattern: // Left-bottom to right-top diagonal lines
1671             case FormatBackground::FDiagPattern: // Left-top to right-bottom diagonal lines
1672             case FormatBackground::CrossPattern: // Horizontal and Vertical lines
1673             case FormatBackground::DiagCrossPattern: { // Crossing diagonal lines
1674                 fillStyle.addProperty("draw:fill", "hatch");
1675                 KoGenStyle hatchStyle(KoGenStyle::HatchStyle);
1676                 hatchStyle.addAttribute("draw:color", "#000000");
1677                 switch (back.pattern()) {
1678                 case FormatBackground::Dense1Pattern:
1679                 case FormatBackground::HorPattern:
1680                     hatchStyle.addAttribute("draw:style", "single");
1681                     hatchStyle.addAttribute("draw:rotation", 0);
1682                     break;
1683                 case FormatBackground::VerPattern:
1684                     hatchStyle.addAttribute("draw:style", "single");
1685                     hatchStyle.addAttribute("draw:rotation", 900);
1686                     break;
1687                 case FormatBackground::Dense2Pattern:
1688                 case FormatBackground::BDiagPattern:
1689                     hatchStyle.addAttribute("draw:style", "single");
1690                     hatchStyle.addAttribute("draw:rotation", 450);
1691                     break;
1692                 case FormatBackground::FDiagPattern:
1693                     hatchStyle.addAttribute("draw:style", "single");
1694                     hatchStyle.addAttribute("draw:rotation", 1350);
1695                     break;
1696                 case FormatBackground::CrossPattern:
1697                     hatchStyle.addAttribute("draw:style", "double");
1698                     hatchStyle.addAttribute("draw:rotation", 0);
1699                     break;
1700                 case FormatBackground::DiagCrossPattern:
1701                     hatchStyle.addAttribute("draw:style", "double");
1702                     hatchStyle.addAttribute("draw:rotation", 450);
1703                     break;
1704                 default:
1705                     break;
1706                 }
1707                 fillStyle.addProperty("draw:fill-hatch-name", mainStyles->insert(hatchStyle, "hatch"));
1708             } break;
1709             default:
1710                 break;
1711         }
1712         style.addProperty("draw:style-name", styles->insert(fillStyle, "gr"));
1713     }
1714 
1715     if (!align.isNull()) {
1716         switch (align.alignX()) {
1717         case Format::Left:
1718             style.addProperty("fo:text-align", "start", KoGenStyle::ParagraphType); break;
1719         case Format::Center:
1720             style.addProperty("fo:text-align", "center", KoGenStyle::ParagraphType); break;
1721         case Format::Right:
1722             style.addProperty("fo:text-align", "end", KoGenStyle::ParagraphType); break;
1723         case Format::Justify:
1724         case Format::Distributed:
1725             style.addProperty("fo:text-align", "justify", KoGenStyle::ParagraphType); break;
1726         }
1727 
1728         if (align.indentLevel() != 0)
1729             style.addProperty("fo:margin-left", QString::number(align.indentLevel()) + "0pt", KoGenStyle::ParagraphType);
1730     }
1731 }
1732 
1733 // 3.8.31 numFmts
processValueFormat(const QString & valueFormat)1734 QString ExcelImport::Private::processValueFormat(const QString& valueFormat)
1735 {
1736     KoGenStyle& style = valueFormatCache[valueFormat];
1737     if (style.isEmpty()) {
1738         style = NumberFormatParser::parse( valueFormat, styles );
1739     }
1740     if( style.type() == KoGenStyle::ParagraphAutoStyle ) {
1741         return QString();
1742     }
1743 
1744     return styles->insert( style, "N" );
1745 }
1746 
createDefaultColumnStyle(Sheet * sheet)1747 void ExcelImport::Private::createDefaultColumnStyle( Sheet* sheet ) {
1748     KoGenStyle style(KoGenStyle::TableColumnAutoStyle, "table-column");
1749 
1750     style.addProperty("fo:break-before", "auto");
1751     style.addPropertyPt("style:column-width", sheet->defaultColWidth() );
1752 
1753     const QString styleName = styles->insert(style, "co");
1754     defaultColumnStyles.append( styleName );
1755 }
1756 
processSheetBackground(Sheet * sheet,KoGenStyle & style)1757 void ExcelImport::Private::processSheetBackground(Sheet* sheet, KoGenStyle& style)
1758 {
1759     if( sheet->backgroundImage().isEmpty() )
1760         return;
1761 
1762     QBuffer buffer;
1763     buffer.open(QIODevice::WriteOnly);
1764     KoXmlWriter writer(&buffer);
1765 
1766     //TODO add the manifest entry
1767     style_background_image bg(&writer);
1768     bg.set_xlink_href(urlFromArg(sheet->backgroundImage()));
1769     bg.set_xlink_type("simple");
1770     bg.set_xlink_show("embed");
1771     bg.set_xlink_actuate("onLoad");
1772     bg.end();
1773 
1774     buffer.close();
1775     style.addChildElement("style:background-image", QString::fromUtf8(buffer.buffer(), buffer.buffer().size()));
1776     manifestEntries.insert(sheet->backgroundImage(), "image/bmp");
1777 }
1778 
addManifestEntries(KoXmlWriter * manifestWriter)1779 void ExcelImport::Private::addManifestEntries(KoXmlWriter* manifestWriter)
1780 {
1781     QMap<QString, QString>::const_iterator iterator = manifestEntries.constBegin();
1782     QMap<QString, QString>::const_iterator end = manifestEntries.constEnd();
1783     while( iterator != end ) {
1784         manifestWriter->addManifestEntry(iterator.key(), iterator.value());
1785         ++iterator;
1786     }
1787 }
1788 
insertPictureManifest(const QString & fileName)1789 void ExcelImport::Private::insertPictureManifest(const QString &fileName)
1790 {
1791     QString mimeType;
1792     const QString extension = fileName.right(fileName.size() - fileName.lastIndexOf('.') - 1);
1793 
1794     if( extension == "gif" ) {
1795         mimeType = "image/gif";
1796     }
1797     else if( extension == "jpg" || extension == "jpeg"
1798             || extension == "jpe" || extension == "jfif" ) {
1799         mimeType = "image/jpeg";
1800     }
1801     else if( extension == "tif" || extension == "tiff" ) {
1802         mimeType = "image/tiff";
1803     }
1804     else if( extension == "png" ) {
1805         mimeType = "image/png";
1806     }
1807     else if( extension == "emf" ) {
1808         mimeType = "application/x-openoffice-wmf;windows_formatname=\"Image EMF\"";
1809     }
1810     else if( extension == "wmf" ) {
1811         mimeType = "application/x-openoffice-wmf;windows_formatname=\"Image WMF\"";
1812     }
1813     else if( extension == "bmp" ) {
1814         mimeType = "image/bmp";
1815     }
1816 
1817     manifestEntries.insert(fileName, mimeType);
1818 }
1819 
1820 #include <excelimporttoods.moc>
1821