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("'Times New Roman'");
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