File indexing completed on 2025-01-19 13:27:37
0001 /* 0002 * This file is part of Office 2007 Filters for Calligra 0003 * 0004 * Copyright (C) 2010 Sebastian Sauer <sebsauer@kdab.com> 0005 * Copyright (C) 2009-2010 Nokia Corporation and/or its subsidiary(-ies). 0006 * 0007 * Contact: Suresh Chande suresh.chande@nokia.com 0008 * 0009 * This library is free software; you can redistribute it and/or 0010 * modify it under the terms of the GNU Lesser General Public License 0011 * version 2.1 as published by the Free Software Foundation. 0012 * 0013 * This library is distributed in the hope that it will be useful, but 0014 * WITHOUT ANY WARRANTY; without even the implied warranty of 0015 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 0016 * Lesser General Public License for more details. 0017 * 0018 * You should have received a copy of the GNU Lesser General Public 0019 * License along with this library; if not, write to the Free Software 0020 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 0021 * 02110-1301 USA 0022 * 0023 */ 0024 0025 // Own 0026 #include "XlsxUtils.h" 0027 #include "XlsxXmlWorksheetReader.h" 0028 0029 #include "XlsxXmlCommentsReader.h" 0030 #include "XlsxXmlStylesReader.h" 0031 #include "XlsxXmlDocumentReader.h" 0032 #include "XlsxXmlDrawingReader.h" 0033 #include "XlsxXmlChartReader.h" 0034 #include "XlsxXmlTableReader.h" 0035 #include "XlsxImport.h" 0036 #include "Charting.h" 0037 #include "XlsxChartOdfWriter.h" 0038 #include "FormulaParser.h" 0039 0040 #include <MsooXmlRelationships.h> 0041 #include <MsooXmlSchemas.h> 0042 #include <MsooXmlUtils.h> 0043 #include <MsooXmlUnits.h> 0044 #include <MsooXmlGlobal.h> 0045 0046 #include <KoUnit.h> 0047 #include <KoXmlWriter.h> 0048 #include <KoGenStyles.h> 0049 #include <styles/KoCharacterStyle.h> 0050 0051 #include <sheets/Util.h> 0052 0053 #include <QBrush> 0054 #include <QRegExp> 0055 #include <QString> 0056 #include <QList> 0057 #include <QCache> 0058 0059 #include "NumberFormatParser.h" 0060 0061 #define XLSXXMLWORKSHEETREADER_CPP 0062 0063 #define UNICODE_EUR 0x20AC 0064 #define UNICODE_GBP 0x00A3 0065 #define UNICODE_JPY 0x00A5 0066 0067 #undef MSOOXML_CURRENT_NS // tags without namespace 0068 #define MSOOXML_CURRENT_CLASS XlsxXmlWorksheetReader 0069 #define BIND_READ_CLASS MSOOXML_CURRENT_CLASS 0070 0071 #include <MsooXmlReader_p.h> 0072 0073 #include <cmath> 0074 #include <algorithm> 0075 0076 // ---------------------------------------------------------------- 0077 // Include implementation of common tags 0078 0079 #include <MsooXmlCommonReaderImpl.h> // this adds p, pPr, t, r, etc. 0080 0081 #undef MSOOXML_CURRENT_NS // tags without namespace 0082 #define MSOOXML_CURRENT_NS 0083 0084 // ---------------------------------------------------------------- 0085 0086 #define NO_DRAWINGML_NS 0087 #define NO_DRAWINGML_PIC_NS // DrawingML/Picture 0088 0089 #include <MsooXmlCommonReaderDrawingMLImpl.h> // this adds pic, etc. 0090 0091 #include "XlsxXmlWorksheetReader_p.h" 0092 0093 XlsxXmlWorksheetReaderContext::XlsxXmlWorksheetReaderContext( 0094 uint _worksheetNumber, 0095 uint _numberOfWorkSheets, 0096 const QString& _worksheetName, 0097 const QString& _state, 0098 const QString _path, const QString _file, 0099 MSOOXML::DrawingMLTheme*& _themes, 0100 const QVector<QString>& _sharedStrings, 0101 const XlsxComments& _comments, 0102 const XlsxStyles& _styles, 0103 MSOOXML::MsooXmlRelationships& _relationships, 0104 XlsxImport* _import, 0105 QMap<QString, QString> _oleReplacements, 0106 QMap<QString, QString> _oleBeginFrames, 0107 QVector<XlsxXmlDocumentReaderContext::AutoFilter>& autoFilters) 0108 : MSOOXML::MsooXmlReaderContext(&_relationships) 0109 , sheet(new Sheet(_worksheetName)) 0110 , worksheetNumber(_worksheetNumber) 0111 , numberOfWorkSheets(_numberOfWorkSheets) 0112 , worksheetName(_worksheetName) 0113 , state(_state) 0114 , themes(_themes) 0115 , sharedStrings(&_sharedStrings) 0116 , comments(&_comments) 0117 , styles(&_styles) 0118 , import(_import) 0119 , path(_path) 0120 , file(_file) 0121 , oleReplacements(_oleReplacements) 0122 , oleFrameBegins(_oleBeginFrames) 0123 , autoFilters(autoFilters) 0124 { 0125 } 0126 0127 XlsxXmlWorksheetReaderContext::~XlsxXmlWorksheetReaderContext() 0128 { 0129 delete sheet; 0130 } 0131 0132 static void splitToRowAndColumn(const char *source, int sourceStart, int sourceLength, QString& row, int& column) 0133 { 0134 // find the position of the first number 0135 int pos = 0; 0136 while (pos < sourceLength) { 0137 if (source[sourceStart + pos] < 65) { 0138 break; 0139 } 0140 row.append(source[sourceStart + pos]); 0141 pos++; 0142 } 0143 0144 char *pEnd = 0; 0145 column = strtol(source + sourceStart + pos, &pEnd, 10); 0146 } 0147 0148 //! @return value @a cm with cm suffix 0149 static QString printCm(double cm) 0150 { 0151 QString string; 0152 string.sprintf("%3.3fcm", cm); 0153 return string; 0154 } 0155 0156 0157 QList<QMap<QString, QString> > XlsxXmlWorksheetReaderContext::conditionalStyleForPosition(const QString& positionLetter, int positionNumber) 0158 { 0159 QString startLetter, endLetter; 0160 int startNumber, endNumber; 0161 0162 QList<QMap<QString, QString> > returnMaps; 0163 0164 // Known positions which are hits/misses for this 0165 // purpose is to optimize this code part for a large set of conditions 0166 QList<QString> cachedHits, cachedMisses; 0167 0168 // We do not wish to add the same condition twice 0169 QList<QString> addedConditions; 0170 0171 int index = 0; 0172 while (index < conditionalStyles.size()) { 0173 startLetter.clear(); 0174 endLetter.clear(); 0175 0176 QString range = conditionalStyles.at(index).first; 0177 if (cachedHits.contains(range)) { 0178 if (!addedConditions.contains(conditionalStyles.at(index).second.value("style:condition"))) { 0179 returnMaps.push_back(conditionalStyles.at(index).second); 0180 addedConditions.push_back(conditionalStyles.at(index).second.value("style:condition")); 0181 } 0182 ++index; 0183 continue; 0184 } 0185 if (cachedMisses.contains(range)) { 0186 ++index; 0187 continue; 0188 } 0189 0190 QByteArray ba = range.toLatin1(); 0191 0192 int columnIndex = ba.indexOf(':'); 0193 if (columnIndex < 0) { 0194 splitToRowAndColumn(ba.constData(), 0, ba.length(), startLetter, startNumber); 0195 endLetter.clear(); 0196 } 0197 else { 0198 splitToRowAndColumn(ba.constData(), 0, columnIndex, startLetter, startNumber); 0199 splitToRowAndColumn(ba.constData(), columnIndex + 1, ba.size() - (columnIndex + 1), endLetter, endNumber); 0200 } 0201 0202 if ((positionLetter == startLetter && positionNumber == startNumber && endLetter.isEmpty()) || 0203 (positionLetter >= startLetter && positionNumber >= startNumber && 0204 positionLetter <= endLetter && positionNumber <= endNumber)) { 0205 if (!addedConditions.contains(conditionalStyles.at(index).second.value("style:condition"))) { 0206 returnMaps.push_back(conditionalStyles.at(index).second); 0207 addedConditions.push_back(conditionalStyles.at(index).second.value("style:condition")); 0208 } 0209 cachedHits.push_back(range); 0210 ++index; 0211 continue; 0212 } 0213 else { 0214 cachedMisses.push_back(range); 0215 ++index; 0216 continue; 0217 } 0218 ++index; 0219 } 0220 0221 return returnMaps; 0222 } 0223 0224 const char XlsxXmlWorksheetReader::officeValue[] = "office:value"; 0225 const char XlsxXmlWorksheetReader::officeDateValue[] = "office:date-value"; 0226 const char XlsxXmlWorksheetReader::officeStringValue[] = "office:string-value"; 0227 const char XlsxXmlWorksheetReader::officeTimeValue[] = "office:time-value"; 0228 const char XlsxXmlWorksheetReader::officeBooleanValue[] = "office:boolean-value"; 0229 0230 class XlsxXmlWorksheetReader::Private 0231 { 0232 public: 0233 Private( XlsxXmlWorksheetReader* qq ) 0234 : q( qq ), 0235 warningAboutWorksheetSizeDisplayed(false), 0236 drawingNumber(0) 0237 { 0238 } 0239 //~Private(){ qDeleteAll( savedStyles ); } 0240 0241 XlsxXmlWorksheetReader* const q; 0242 bool warningAboutWorksheetSizeDisplayed; 0243 int drawingNumber; 0244 QHash<int, Cell*> sharedFormulas; 0245 QHash<QString, QString > savedStyles; 0246 }; 0247 0248 XlsxXmlWorksheetReader::XlsxXmlWorksheetReader(KoOdfWriters *writers) 0249 : MSOOXML::MsooXmlCommonReader(writers) 0250 , m_context(0) 0251 , d(new Private( this ) ) 0252 { 0253 init(); 0254 } 0255 0256 XlsxXmlWorksheetReader::~XlsxXmlWorksheetReader() 0257 { 0258 delete d; 0259 } 0260 0261 void XlsxXmlWorksheetReader::init() 0262 { 0263 initInternal(); // MsooXmlCommonReaderImpl.h 0264 initDrawingML(); 0265 m_defaultNamespace = ""; 0266 m_columnCount = 0; 0267 m_currentRow = 0; 0268 m_currentColumn = 0; 0269 } 0270 0271 KoFilter::ConversionStatus XlsxXmlWorksheetReader::read(MSOOXML::MsooXmlReaderContext* context) 0272 { 0273 m_context = dynamic_cast<XlsxXmlWorksheetReaderContext*>(context); 0274 Q_ASSERT(m_context); 0275 const KoFilter::ConversionStatus result = readInternal(); 0276 m_context = 0; 0277 0278 return result; 0279 } 0280 0281 KoFilter::ConversionStatus XlsxXmlWorksheetReader::readInternal() 0282 { 0283 qCDebug(lcXlsxImport) << "============================="; 0284 Q_ASSERT(m_context); 0285 0286 readNext(); 0287 if (!isStartDocument()) { 0288 return KoFilter::WrongFormat; 0289 } 0290 0291 // worksheet 0292 readNext(); 0293 //qCDebug(lcXlsxImport) << *this << namespaceUri(); 0294 0295 if (name() != "worksheet" && name() != "dialogsheet" && name() != "chartsheet") { 0296 return KoFilter::WrongFormat; 0297 } 0298 if (!expectNS(MSOOXML::Schemas::spreadsheetml)) { 0299 return KoFilter::WrongFormat; 0300 } 0301 0302 m_context->sheet->setVisible( m_context->state.toLower() != "hidden" ); 0303 0304 QXmlStreamNamespaceDeclarations namespaces(namespaceDeclarations()); 0305 for (int i = 0; i < namespaces.count(); i++) { 0306 qCDebug(lcXlsxImport) << "NS prefix:" << namespaces[i].prefix() << "uri:" << namespaces[i].namespaceUri(); 0307 } 0308 //! @todo find out whether the namespace returned by namespaceUri() 0309 //! is exactly the same ref as the element of namespaceDeclarations() 0310 if (!namespaces.contains(QXmlStreamNamespaceDeclaration("", MSOOXML::Schemas::spreadsheetml))) { 0311 raiseError(i18n("Namespace \"%1\" not found", QLatin1String(MSOOXML::Schemas::spreadsheetml))); 0312 return KoFilter::WrongFormat; 0313 } 0314 //! @todo expect other namespaces too... 0315 0316 if (name() == "worksheet") { 0317 TRY_READ(worksheet) 0318 } 0319 else if (name() == "dialogsheet") { 0320 TRY_READ(dialogsheet) 0321 } 0322 0323 qCDebug(lcXlsxImport) << "===========finished============"; 0324 return KoFilter::OK; 0325 } 0326 0327 0328 QString XlsxXmlWorksheetReader::computeColumnWidth(qreal widthNumber) const 0329 { 0330 //! CASE #S3300 0331 //! Column width measured as the number of characters of the maximum digit width of the 0332 //! numbers 0, 1, 2, …, 9 as rendered in the normal style's font. There are 4 pixels of margin 0333 //! padding (two on each side), plus 1 pixel padding for the gridlines. 0334 //! For explanation of width, see p. 1778 0335 //simplified: 0336 //! @todo hardcoded, not 100% accurate 0337 qCDebug(lcXlsxImport) << "PT_TO_PX(11.0):" << PT_TO_PX(11.0); 0338 const double realSize = round(PT_TO_PX(11.0)) * 0.75; 0339 qCDebug(lcXlsxImport) << "realSize:" << realSize; 0340 const double averageDigitWidth = realSize * 2.0 / 3.0; 0341 qCDebug(lcXlsxImport) << "averageDigitWidth:" << averageDigitWidth; 0342 0343 QString result; 0344 if (averageDigitWidth * widthNumber == 0) { 0345 result = QLatin1String("0cm"); 0346 } 0347 else 0348 { 0349 result = printCm(PX_TO_CM(averageDigitWidth * widthNumber)); 0350 } 0351 0352 return result; 0353 } 0354 0355 void XlsxXmlWorksheetReader::showWarningAboutWorksheetSize() 0356 { 0357 if (d->warningAboutWorksheetSizeDisplayed) 0358 return; 0359 d->warningAboutWorksheetSizeDisplayed = true; 0360 qCWarning(lcXlsxImport) << i18n("The data could not be loaded completely because the maximum size of " 0361 "sheet was exceeded."); 0362 } 0363 0364 inline static QString encodeLabelText(int col, int row) 0365 { 0366 return Calligra::Sheets::Util::encodeColumnLabelText(col) + QString::number(row); 0367 } 0368 0369 void XlsxXmlWorksheetReader::saveAnnotation(int col, int row) 0370 { 0371 QString ref(encodeLabelText(col + 1, row + 1)); 0372 qCDebug(lcXlsxImport) << ref; 0373 XlsxComment *comment = m_context->comments->value(ref); 0374 if (!comment) 0375 return; 0376 //qCDebug(lcXlsxImport) << "Saving annotation for cell" << ref; 0377 body->startElement("office:annotation"); 0378 body->startElement("dc:creator"); 0379 body->addTextNode(comment->author(m_context->comments)); 0380 body->endElement(); // dc:creator 0381 //! @todo support dc:date 0382 body->startElement("text:p"); 0383 body->addCompleteElement(comment->texts.toUtf8()); 0384 body->endElement(); // text:p 0385 body->endElement(); // office:annotation 0386 } 0387 0388 #undef CURRENT_EL 0389 #define CURRENT_EL chartsheet 0390 KoFilter::ConversionStatus XlsxXmlWorksheetReader::read_chartsheet() 0391 { 0392 READ_PROLOGUE 0393 0394 return read_sheetHelper("chartsheet"); 0395 0396 READ_EPILOGUE 0397 } 0398 0399 #undef CURRENT_EL 0400 #define CURRENT_EL dialogsheet 0401 KoFilter::ConversionStatus XlsxXmlWorksheetReader::read_dialogsheet() 0402 { 0403 READ_PROLOGUE 0404 0405 return read_sheetHelper("dialogsheet"); 0406 0407 READ_EPILOGUE 0408 } 0409 0410 #undef CURRENT_EL 0411 #define CURRENT_EL worksheet 0412 //! worksheet handler (Worksheet) 0413 /*! ECMA-376, 18.3.1.99, p. 1894. 0414 Root element of Worksheet parts within a SpreadsheetML document. 0415 Child elements: 0416 - [done] autoFilter (AutoFilter Settings) §18.3.1.2 0417 - cellWatches (Cell Watch Items) §18.3.1.9 0418 - colBreaks (Vertical Page Breaks) §18.3.1.14 0419 - [done] cols (Column Information) §18.3.1.17 0420 - [done] conditionalFormatting (Conditional Formatting) §18.3.1.18 0421 - [done] controls (Embedded Controls) §18.3.1.21 0422 - customProperties (Custom Properties) §18.3.1.23 0423 - customSheetViews (Custom Sheet Views) §18.3.1.27 0424 - dataConsolidate (Data Consolidate) §18.3.1.29 0425 - dataValidations (Data Validations) §18.3.1.33 0426 - dimension (Worksheet Dimensions) §18.3.1.35 0427 - [done] drawing (Drawing) §18.3.1.36 0428 - drawingHF (Drawing Reference in Header Footer) §18.3.1.37 0429 - extLst (Future Feature Data Storage Area) §18.2.10 0430 - headerFooter (Header Footer Settings) §18.3.1.46 0431 - [done] hyperlinks (Hyperlinks) §18.3.1.48 0432 - ignoredErrors (Ignored Errors) §18.3.1.51 0433 - [done] mergeCells (Merge Cells) §18.3.1.55 0434 - [done] oleObjects (Embedded Objects) §18.3.1.60 0435 - pageMargins (Page Margins) §18.3.1.62 0436 - pageSetup (Page Setup Settings) §18.3.1.63 0437 - phoneticPr (Phonetic Properties) §18.4.3 0438 - [done] picture (Background Image) §18.3.1.67 0439 - printOptions (Print Options) §18.3.1.70 0440 - protectedRanges (Protected Ranges) §18.3.1.72 0441 - rowBreaks (Horizontal Page Breaks (Row)) §18.3.1.74 0442 - scenarios (Scenarios) §18.3.1.76 0443 - sheetCalcPr (Sheet Calculation Properties) §18.3.1.79 0444 - [done] sheetData (Sheet Data) §18.3.1.80 0445 - sheetFormatPr (Sheet Format Properties) §18.3.1.81 0446 - sheetPr (Sheet Properties) §18.3.1.82 0447 - sheetProtection (Sheet Protection Options) §18.3.1.85 0448 - sheetViews (Sheet Views) §18.3.1.88 0449 - smartTags (Smart Tags) §18.3.1.90 0450 - sortState (Sort State) §18.3.1.92 0451 - [done] tableParts (Table Parts) §18.3.1.95 0452 - webPublishItems (Web Publishing Items) §18.3.1.98 0453 0454 @todo support all child elements 0455 */ 0456 KoFilter::ConversionStatus XlsxXmlWorksheetReader::read_worksheet() 0457 { 0458 READ_PROLOGUE 0459 0460 return read_sheetHelper("worksheet"); 0461 0462 READ_EPILOGUE 0463 } 0464 0465 KoFilter::ConversionStatus XlsxXmlWorksheetReader::read_sheetHelper(const QString& type) 0466 { 0467 // In the first round we do not wish to output anything 0468 QBuffer fakeBuffer; 0469 KoXmlWriter fakeBody(&fakeBuffer); 0470 KoXmlWriter *oldBody = body; 0471 if (m_context->firstRoundOfReading) { 0472 body = &fakeBody; 0473 } 0474 0475 body->startElement("table:table"); 0476 0477 //! @todo implement CASE #S202 for fixing the name 0478 body->addAttribute("table:name", m_context->worksheetName); 0479 0480 m_tableStyle = KoGenStyle(KoGenStyle::TableAutoStyle, "table"); 0481 //! @todo hardcoded master page name 0482 m_tableStyle.addAttribute("style:master-page-name", 0483 QString("PageStyle_5f_Test_20_sheet_20__5f_%1").arg(m_context->worksheetNumber)); 0484 0485 m_tableStyle.addProperty("table:display", m_context->sheet->visible()); 0486 0487 //The style might be changed depending on what elements we find, 0488 //hold the body writer so that we can set the proper style 0489 KoXmlWriter* heldBody = body; 0490 QBuffer bodyBuffer; 0491 bodyBuffer.open(QIODevice::ReadWrite); 0492 body = new KoXmlWriter(&bodyBuffer); 0493 0494 QBuffer drawingBuffer; 0495 drawingBuffer.open(QIODevice::ReadWrite); 0496 0497 while (!atEnd()) { 0498 readNext(); 0499 qCDebug(lcXlsxImport) << *this; 0500 if (isEndElement() && name() == type) { 0501 break; 0502 } 0503 if (isStartElement() && !m_context->firstRoundOfReading) { 0504 TRY_READ_IF(sheetFormatPr) 0505 ELSE_TRY_READ_IF(cols) 0506 ELSE_TRY_READ_IF(sheetData) // does fill the m_context->sheet 0507 ELSE_TRY_READ_IF(mergeCells) 0508 else if (name() == "drawing") { 0509 KoXmlWriter *tempBodyHolder = body; 0510 body = new KoXmlWriter(&drawingBuffer); 0511 TRY_READ(drawing) 0512 delete body; 0513 body = tempBodyHolder; 0514 } 0515 ELSE_TRY_READ_IF(legacyDrawing) 0516 ELSE_TRY_READ_IF(hyperlinks) 0517 ELSE_TRY_READ_IF(picture) 0518 ELSE_TRY_READ_IF(oleObjects) 0519 else if (name() == "controls") { 0520 KoXmlWriter *tempBodyHolder = body; 0521 body = new KoXmlWriter(&drawingBuffer); 0522 TRY_READ(controls) 0523 delete body; 0524 body = tempBodyHolder; 0525 } 0526 ELSE_TRY_READ_IF(autoFilter) 0527 SKIP_UNKNOWN 0528 } 0529 else if (isStartElement() && m_context->firstRoundOfReading) { 0530 TRY_READ_IF(conditionalFormatting) 0531 ELSE_TRY_READ_IF(tableParts) 0532 SKIP_UNKNOWN 0533 } 0534 } 0535 0536 if (m_context->firstRoundOfReading) { 0537 // Sorting conditional styles according to the priority 0538 0539 typedef QPair<int, QMap<QString, QString> > Condition; 0540 0541 // Transforming to a list for easier handling 0542 QList<QPair<QPair<QString, QMap<QString, QString> >, int> > diffFormulasList; 0543 QMapIterator<QString, QList<Condition> > i(m_conditionalStyles); 0544 while (i.hasNext()) { 0545 i.next(); 0546 int index = 0; 0547 QList<Condition> conditions = i.value(); 0548 QPair<QString, QMap<QString, QString> > innerPair; 0549 QPair<QPair<QString, QMap<QString, QString> >, int> outerPair; 0550 0551 while (index < conditions.size()) { 0552 innerPair.first = i.key(); 0553 innerPair.second = conditions.at(index).second; 0554 outerPair.first = innerPair; 0555 outerPair.second = conditions.at(index).first; 0556 diffFormulasList.push_back(outerPair); 0557 ++index; 0558 } 0559 } 0560 QList<QPair<int, int> > priorityActualIndex; 0561 int index = 0; 0562 while (index < diffFormulasList.size()) { 0563 priorityActualIndex.push_back(QPair<int, int>(diffFormulasList.at(index).second, index)); 0564 ++index; 0565 } 0566 std::sort(priorityActualIndex.begin(), priorityActualIndex.end()); 0567 0568 // Finally we have the list sorted and we can store the conditions in right priority order 0569 index = 0; 0570 while (index < priorityActualIndex.size()) { 0571 QPair<QString, QMap<QString, QString> > odfValue; 0572 odfValue.first = diffFormulasList.at(priorityActualIndex.at(index).second).first.first; 0573 odfValue.second = diffFormulasList.at(priorityActualIndex.at(index).second).first.second; 0574 m_context->conditionalStyles.push_back(odfValue); 0575 ++index; 0576 } 0577 } 0578 0579 if( !m_context->sheet->pictureBackgroundPath().isNull() ) { 0580 QBuffer buffer; 0581 buffer.open(QIODevice::WriteOnly); 0582 KoXmlWriter writer(&buffer); 0583 0584 writer.startElement("style:background-image"); 0585 writer.addAttribute("xlink:href", m_context->sheet->pictureBackgroundPath()); 0586 writer.addAttribute("xlink:type", "simple"); 0587 writer.addAttribute("xlink:show", "embed"); 0588 writer.addAttribute("xlink:actuate", "onLoad"); 0589 writer.endElement(); 0590 0591 buffer.close(); 0592 m_tableStyle.addChildElement("style:background-image", QString::fromUtf8(buffer.buffer(), buffer.buffer().size())); 0593 } 0594 0595 const QString currentTableStyleName(mainStyles->insert(m_tableStyle, "ta")); 0596 heldBody->addAttribute("table:style-name", currentTableStyleName); 0597 heldBody->addCompleteElement(&bodyBuffer); 0598 delete body; 0599 body = heldBody; 0600 0601 // Adding drawings, if there are any 0602 if (drawingBuffer.size() > 0) { 0603 body->startElement("table:shapes"); 0604 body->addCompleteElement(&drawingBuffer); 0605 body->endElement(); // table:shapes 0606 } 0607 0608 // now we have everything to start writing the actual cells 0609 int c = 0; 0610 while (c <= m_context->sheet->maxColumn()) { 0611 body->startElement("table:table-column"); 0612 int repeatedColumns = 1; 0613 bool currentColumnHidden = false; 0614 Column* column = m_context->sheet->column(c, false); 0615 if (column) { 0616 if (!column->styleName.isEmpty()) { 0617 body->addAttribute("table:style-name", column->styleName); 0618 } else { 0619 if (m_context->sheet->m_defaultColWidth != -1.0) { 0620 saveColumnStyle( computeColumnWidth( m_context->sheet->m_defaultColWidth ) ); 0621 } 0622 } 0623 } 0624 0625 if (column && column->hidden) { 0626 body->addAttribute("table:visibility", "collapse"); 0627 currentColumnHidden = true; 0628 } 0629 ++c; 0630 while (c <= m_context->sheet->maxColumn()) { 0631 column = m_context->sheet->column(c, false); 0632 if (column && column->hidden ) { 0633 if (currentColumnHidden) { 0634 ++repeatedColumns; 0635 } 0636 else { 0637 break; 0638 } 0639 } 0640 else { 0641 if (!currentColumnHidden) { 0642 ++repeatedColumns; 0643 } 0644 else { 0645 break; 0646 } 0647 } 0648 ++c; 0649 } 0650 if (repeatedColumns > 1) { 0651 body->addAttribute("table:number-columns-repeated", repeatedColumns); 0652 } 0653 body->endElement(); // table:table-column 0654 } 0655 0656 const int rowCount = m_context->sheet->maxRow(); 0657 for(int r = 0; r <= rowCount; ++r) { 0658 const int columnCount = m_context->sheet->maxCellsInRow(r); 0659 Row* row = m_context->sheet->row(r, false); 0660 body->startElement("table:table-row"); 0661 if (row) { 0662 if (!row->styleName.isEmpty()) { 0663 body->addAttribute("table:style-name", row->styleName); 0664 } else if (m_context->sheet->m_defaultRowHeight != -1.0) { 0665 QString styleName = processRowStyle(m_context->sheet->m_defaultRowHeight); // in pt 0666 body->addAttribute("table:style-name", styleName); 0667 } 0668 0669 if (row->hidden) { 0670 body->addAttribute("table:visibility", "collapse"); 0671 } 0672 //body->addAttribute("table:number-rows-repeated", QByteArray::number(row->repeated)); 0673 0674 for(int c = 0; c <= columnCount; ++c) { 0675 body->startElement("table:table-cell"); 0676 if (Cell* cell = m_context->sheet->cell(c, r, false)) { 0677 const bool hasHyperlink = ! cell->hyperlink().isEmpty(); 0678 0679 if (!cell->styleName.isEmpty()) { 0680 body->addAttribute("table:style-name", cell->styleName); 0681 } 0682 //body->addAttribute("table:number-columns-repeated", QByteArray::number(cell->repeated)); 0683 if (!hasHyperlink) { 0684 switch(cell->valueType) { 0685 case Cell::ConstNone: 0686 break; 0687 case Cell::ConstString: 0688 body->addAttribute("office:value-type", MsooXmlReader::constString); 0689 break; 0690 case Cell::ConstBoolean: 0691 body->addAttribute("office:value-type", MsooXmlReader::constBoolean); 0692 break; 0693 case Cell::ConstDate: 0694 body->addAttribute("office:value-type", MsooXmlReader::constDate); 0695 break; 0696 case Cell::ConstFloat: 0697 body->addAttribute("office:value-type", MsooXmlReader::constFloat); 0698 break; 0699 } 0700 } 0701 0702 if (cell->valueAttrValue) { 0703 switch(cell->valueAttr) { 0704 case Cell::OfficeNone: 0705 break; 0706 case Cell::OfficeValue: 0707 body->addAttribute(XlsxXmlWorksheetReader::officeValue, *cell->valueAttrValue); 0708 break; 0709 case Cell::OfficeStringValue: 0710 body->addAttribute(XlsxXmlWorksheetReader::officeStringValue, *cell->valueAttrValue); 0711 break; 0712 case Cell::OfficeBooleanValue: 0713 // Treat boolean values specially (ODF1.1 chapter 6.7.1) 0714 //! @todo This breaks down if the value is a formula and not constant. 0715 body->addAttribute(XlsxXmlWorksheetReader::officeBooleanValue, 0716 *cell->valueAttrValue == "0" ? "false" : "true"); 0717 break; 0718 case Cell::OfficeDateValue: 0719 body->addAttribute(XlsxXmlWorksheetReader::officeDateValue, *cell->valueAttrValue); 0720 break; 0721 } 0722 } 0723 0724 if (cell->formula) { 0725 QString formula; 0726 if (cell->formula->isShared()) { 0727 Cell *referencedCell = static_cast<SharedFormula*>(cell->formula)->m_referencedCell; 0728 Q_ASSERT(referencedCell); 0729 formula = MSOOXML::convertFormulaReference(referencedCell, cell); 0730 } else { 0731 formula = static_cast<FormulaImpl*>(cell->formula)->m_formula; 0732 } 0733 if (!formula.isEmpty()) { 0734 body->addAttribute("table:formula", formula); 0735 } 0736 } 0737 0738 if (cell->rowsMerged > 1) { 0739 body->addAttribute("table:number-rows-spanned", cell->rowsMerged); 0740 } 0741 if (cell->columnsMerged > 1) { 0742 body->addAttribute("table:number-columns-spanned", cell->columnsMerged); 0743 } 0744 0745 saveAnnotation(c, r); 0746 0747 if (!cell->text.isEmpty() || !cell->charStyleName.isEmpty() || hasHyperlink) { 0748 body->startElement("text:p", false); 0749 if (!cell->charStyleName.isEmpty()) { 0750 body->startElement( "text:span" ); 0751 body->addAttribute( "text:style-name", cell->charStyleName); 0752 } 0753 if (hasHyperlink) { 0754 body->startElement("text:a"); 0755 body->addAttribute("xlink:href", cell->hyperlink()); 0756 body->addAttribute("xlink:type", "simple"); 0757 //body->addAttribute("office:target-frame-name", targetFrameName); 0758 if(cell->text.isEmpty()) { 0759 body->addTextNode(cell->hyperlink()); 0760 } 0761 else { 0762 body->addCompleteElement(cell->text.toUtf8()); 0763 } 0764 body->endElement(); // text:a 0765 } else if (!cell->text.isEmpty()) { 0766 body->addCompleteElement(cell->text.toUtf8()); 0767 } 0768 if (!cell->charStyleName.isEmpty()) { 0769 body->endElement(); // text:span 0770 } 0771 body->endElement(); // text:p 0772 } 0773 0774 // handle drawing objects like e.g. charts, diagrams and pictures 0775 if ( cell->embedded ) { 0776 foreach(XlsxDrawingObject* drawing, cell->embedded->drawings) { 0777 drawing->save(body); 0778 } 0779 0780 typedef QPair<QString,QString> OleObject; 0781 int listIndex = 0; 0782 foreach( const OleObject& oleObject, cell->embedded->oleObjects ) { 0783 const QString olePath = oleObject.first; 0784 const QString previewPath = oleObject.second; 0785 body->addCompleteElement(cell->embedded->oleFrameBegins.at(listIndex).toUtf8()); 0786 ++listIndex; 0787 0788 body->startElement("draw:object-ole"); 0789 body->addAttribute("xlink:href", olePath); 0790 body->addAttribute("xlink:type", "simple"); 0791 body->addAttribute("xlink:show", "embed"); 0792 body->addAttribute("xlink:actuate", "onLoad"); 0793 body->endElement(); // draw:object-ole 0794 0795 body->startElement("draw:image"); 0796 body->addAttribute("xlink:href", previewPath); 0797 body->addAttribute("xlink:type", "simple"); 0798 body->addAttribute("xlink:show", "embed"); 0799 body->addAttribute("xlink:actuate", "onLoad"); 0800 body->endElement(); // draw:image 0801 0802 body->addCompleteElement("</draw:frame>"); 0803 } 0804 } 0805 } 0806 body->endElement(); // table:table-cell 0807 } 0808 } 0809 0810 if (!row || columnCount <= 0) { 0811 // element table:table-row may not be empty 0812 body->startElement("table:table-cell"); 0813 body->endElement(); // table:table-cell 0814 } 0815 body->endElement(); // table:table-row 0816 } 0817 0818 body->endElement(); // table:table 0819 0820 if (m_context->firstRoundOfReading) { 0821 body = oldBody; 0822 } 0823 0824 0825 0826 return KoFilter::OK; 0827 } 0828 0829 #undef CURRENT_EL 0830 #define CURRENT_EL conditionalFormatting 0831 /* 0832 Parent elements: 0833 - [done] worksheet (§18.3.1.99) 0834 0835 Child elements: 0836 - [done] cfRule (Conditional Formatting Rule) §18.3.1.10 0837 - extLst (Future Feature Data Storage Area) §18.2.10 0838 0839 */ 0840 KoFilter::ConversionStatus XlsxXmlWorksheetReader::read_conditionalFormatting() 0841 { 0842 READ_PROLOGUE 0843 0844 const QXmlStreamAttributes attrs(attributes()); 0845 TRY_READ_ATTR_WITHOUT_NS(sqref) 0846 0847 // Getting rid of previously handled conditions 0848 m_conditionalIndices.clear(); 0849 0850 while (!atEnd()) { 0851 readNext(); 0852 BREAK_IF_END_OF(CURRENT_EL) 0853 if (isStartElement()) { 0854 TRY_READ_IF(cfRule) 0855 SKIP_UNKNOWN 0856 } 0857 } 0858 0859 QList<QString> areas; 0860 while (sqref.indexOf(' ') > 0) { 0861 QString conditionArea = sqref.left(sqref.indexOf(' ')); 0862 sqref.remove(0, conditionArea.length() + 1); 0863 areas.push_back(conditionArea); 0864 } 0865 areas.push_back(sqref); 0866 0867 typedef QPair<int, QMap<QString, QString> > Condition; 0868 0869 // Adding conditions to list of conditions and making sure that only the one with highest priority 0870 // remains if there are multiple conditions with same area & condition 0871 // This is done because some ooxml files have same condition for some area listed multiple times but 0872 // with different priorities 0873 int index = 0; 0874 while (index < m_conditionalIndices.size()) { 0875 QString conditionalArea; 0876 Condition examinedCondition = m_conditionalIndices.at(index); 0877 QString sqrefOriginal = sqref; 0878 int areaIndex = 0; 0879 Condition previousCond; 0880 0881 while (areaIndex < areas.size()) { 0882 conditionalArea = areas.at(areaIndex); 0883 QList<Condition> previousConditions = m_conditionalStyles.value(conditionalArea); 0884 if (previousConditions.isEmpty()) { 0885 previousConditions.push_back(examinedCondition); 0886 m_conditionalStyles[conditionalArea] = previousConditions; 0887 } 0888 else { 0889 int conditionIndex = 0; 0890 bool hasTheSameCondition = false; 0891 while (conditionIndex < previousConditions.size()) { 0892 // When comparing we only care about the condition, not the style 0893 if (previousConditions.at(conditionIndex).second.value("style:condition") == 0894 examinedCondition.second.value("style:condition")) { 0895 hasTheSameCondition = true; 0896 previousCond = previousConditions.at(conditionIndex); 0897 if (previousCond.first > examinedCondition.first) { 0898 previousConditions.replace(conditionIndex, examinedCondition); 0899 m_conditionalStyles[conditionalArea] = previousConditions; 0900 } 0901 break; 0902 } 0903 ++conditionIndex; 0904 } 0905 0906 if (!hasTheSameCondition) { 0907 previousConditions.push_back(examinedCondition); 0908 m_conditionalStyles[conditionalArea] = previousConditions; 0909 } 0910 } 0911 ++areaIndex; 0912 } 0913 ++index; 0914 } 0915 READ_EPILOGUE 0916 } 0917 0918 #undef CURRENT_EL 0919 #define CURRENT_EL cfRule 0920 /* 0921 Parent elements: 0922 - [done] conditionalFormatting (§18.3.1.18) 0923 0924 Child elements: 0925 - colorScale (Color Scale) §18.3.1.16 0926 - dataBar (Data Bar) §18.3.1.28 0927 - extLst (Future Feature Data Storage Area) §18.2.10 0928 - [done] formula (Formula) §18.3.1.43 0929 - iconSet (Icon Set) §18.3.1.49 0930 0931 */ 0932 KoFilter::ConversionStatus XlsxXmlWorksheetReader::read_cfRule() 0933 { 0934 READ_PROLOGUE 0935 0936 const QXmlStreamAttributes attrs(attributes()); 0937 TRY_READ_ATTR_WITHOUT_NS(type) 0938 TRY_READ_ATTR_WITHOUT_NS(dxfId) 0939 TRY_READ_ATTR_WITHOUT_NS(priority) 0940 QString op = attrs.value("operator").toString(); 0941 0942 QList<QString> formulas; 0943 0944 while (!atEnd()) { 0945 readNext(); 0946 BREAK_IF_END_OF(CURRENT_EL) 0947 if (isStartElement()) { 0948 if (name() == "formula") { 0949 TRY_READ(formula) 0950 formulas.push_back(m_formula); 0951 } 0952 SKIP_UNKNOWN 0953 } 0954 } 0955 0956 QMap<QString, QString> odf; 0957 // TODO, use attributes to really interpret this 0958 // The default one here is valid for type="cellIs" operator="equal" 0959 if (op == "equal") { 0960 odf["style:condition"] = QString("cell-content()=%1").arg(m_formula); 0961 } 0962 else if (op == "lessThan") { 0963 odf["style:condition"] = QString("cell-content()<%1").arg(m_formula); 0964 } 0965 else if (op == "greaterThan") { 0966 odf["style:condition"] = QString("cell-content()>%1").arg(m_formula); 0967 } 0968 else if (op == "between") { 0969 odf["style:condition"] = QString("cell-content-is-between(%1, %2)").arg(formulas.at(0)).arg(formulas.at(1)); 0970 } 0971 odf["style:apply-style-name"] = m_context->styles->conditionalStyle(dxfId.toInt() + 1); 0972 0973 m_conditionalIndices.push_back(QPair<int, QMap<QString, QString> >(priority.toInt(), odf)); 0974 0975 READ_EPILOGUE 0976 } 0977 0978 #undef CURRENT_EL 0979 #define CURRENT_EL formula 0980 /* 0981 Parent elements: 0982 - [done] cfRule (§18.3.1.10) 0983 - rdn (§18.11.1.13) 0984 0985 Child elements: 0986 - none 0987 0988 */ 0989 KoFilter::ConversionStatus XlsxXmlWorksheetReader::read_formula() 0990 { 0991 READ_PROLOGUE 0992 0993 READ_PROLOGUE 0994 while (!atEnd()) { 0995 readNext(); 0996 if (isCharacters()) { 0997 m_formula = text().toString(); 0998 } 0999 BREAK_IF_END_OF(CURRENT_EL) 1000 } 1001 READ_EPILOGUE 1002 } 1003 1004 #undef CURRENT_EL 1005 #define CURRENT_EL sheetFormatPr 1006 //! sheetFormatPr handler (Sheet Format Properties) 1007 /*! ECMA-376, 18.3.1.81, p. 1866. 1008 Sheet formatting properties. 1009 1010 No child elements. 1011 1012 Parent elements: 1013 - dialogsheet (§18.3.1.34) 1014 - [done] worksheet (§18.3.1.99) 1015 1016 @todo support all attributes and elements 1017 */ 1018 KoFilter::ConversionStatus XlsxXmlWorksheetReader::read_sheetFormatPr() 1019 { 1020 READ_PROLOGUE 1021 const QXmlStreamAttributes attrs(attributes()); 1022 TRY_READ_ATTR_WITHOUT_NS(defaultRowHeight) // in pt 1023 TRY_READ_ATTR_WITHOUT_NS(defaultColWidth) 1024 TRY_READ_ATTR_WITHOUT_NS(baseColWidth) 1025 bool ok; 1026 1027 const double drh = defaultRowHeight.toDouble(&ok); 1028 if (ok) { 1029 m_context->sheet->m_defaultRowHeight = drh; 1030 } 1031 1032 const double dcw = defaultColWidth.toDouble(&ok); 1033 if (ok) { 1034 m_context->sheet->m_defaultColWidth = dcw; 1035 } 1036 1037 const double bcw = baseColWidth.toDouble(&ok); 1038 if (ok) { 1039 m_context->sheet->m_baseColWidth = bcw; 1040 } 1041 1042 readNext(); 1043 READ_EPILOGUE 1044 } 1045 1046 #undef CURRENT_EL 1047 #define CURRENT_EL cols 1048 //! cols handler (Column Information) 1049 /*! ECMA-376, 18.3.1.17, p. 1782. 1050 Information about whole columns of the worksheet. 1051 Child elements: 1052 - [done] col (Column Width & Formatting) §18.3.1.13 1053 1054 Parent elements: 1055 - [done] worksheet (§18.3.1.99) 1056 */ 1057 KoFilter::ConversionStatus XlsxXmlWorksheetReader::read_cols() 1058 { 1059 READ_PROLOGUE 1060 while (!atEnd()) { 1061 readNext(); 1062 qCDebug(lcXlsxImport) << *this; 1063 BREAK_IF_END_OF(CURRENT_EL) 1064 if (isStartElement()) { 1065 TRY_READ_IF(col) 1066 ELSE_WRONG_FORMAT 1067 } 1068 } 1069 READ_EPILOGUE_WITHOUT_RETURN 1070 1071 // append remaining empty columns 1072 appendTableColumns(MSOOXML::maximumSpreadsheetColumns() - m_columnCount); 1073 return KoFilter::OK; 1074 } 1075 1076 //! Saves information about column style 1077 void XlsxXmlWorksheetReader::saveColumnStyle(const QString& widthString) 1078 { 1079 if ( !d->savedStyles.contains( widthString ) ) 1080 { 1081 KoGenStyle tableColumnStyle(KoGenStyle::TableColumnAutoStyle, "table-column"); 1082 tableColumnStyle.addProperty("style:column-width", widthString); 1083 tableColumnStyle.addProperty("fo:break-before", "auto"); 1084 1085 const QString currentTableColumnStyleName(mainStyles->insert(tableColumnStyle, "co")); 1086 body->addAttribute("table:style-name", currentTableColumnStyleName); 1087 d->savedStyles[widthString] = currentTableColumnStyleName; 1088 } 1089 else 1090 { 1091 const QString currentTableColumnStyleName(d->savedStyles[widthString]); 1092 body->addAttribute("table:style-name", currentTableColumnStyleName); 1093 } 1094 } 1095 1096 void XlsxXmlWorksheetReader::appendTableColumns(int columns, const QString& width) 1097 { 1098 qCDebug(lcXlsxImport) << "columns:" << columns; 1099 if (columns <= 0) 1100 return; 1101 body->startElement("table:table-column"); 1102 if (columns > 1) 1103 body->addAttribute("table:number-columns-repeated", QByteArray::number(columns)); 1104 //! @todo hardcoded table:default-cell-style-name 1105 body->addAttribute("table:default-cell-style-name", "Excel_20_Built-in_20_Normal"); 1106 //! @todo hardcoded default style:column-width 1107 saveColumnStyle(width.isEmpty() ? QLatin1String("1.707cm") : width); 1108 body->endElement(); // table:table-column 1109 } 1110 1111 #undef CURRENT_EL 1112 #define CURRENT_EL col 1113 //! col handler (Column Width & Formatting) 1114 /*! ECMA-376, 18.3.1.13, p. 1777. 1115 Defines column width and column formatting for one or more columns of the worksheet. 1116 No child elements. 1117 1118 Parent elements: 1119 - [done] cols (§18.3.1.17) 1120 1121 @todo support more attributes 1122 */ 1123 KoFilter::ConversionStatus XlsxXmlWorksheetReader::read_col() 1124 { 1125 READ_PROLOGUE 1126 1127 const QXmlStreamAttributes attrs(attributes()); 1128 1129 Column* column = m_context->sheet->column(m_columnCount, true); 1130 ++m_columnCount; 1131 1132 //moved body->startElement("table:table-column"); // CASE #S2500? 1133 int minCol = m_columnCount; 1134 int maxCol = m_columnCount; 1135 QString minStr, maxStr; 1136 TRY_READ_ATTR_WITHOUT_NS_INTO(min, minStr) 1137 STRING_TO_INT(minStr, minCol, "col@min") 1138 TRY_READ_ATTR_WITHOUT_NS_INTO(max, maxStr) 1139 STRING_TO_INT(maxStr, maxCol, "col@min") 1140 if (minCol > maxCol) 1141 qSwap(minCol, maxCol); 1142 1143 if (m_columnCount < minCol) { 1144 appendTableColumns(minCol - m_columnCount); 1145 m_columnCount = minCol; 1146 } 1147 1148 TRY_READ_ATTR_WITHOUT_NS(width) 1149 QString realWidthString; 1150 if (!width.isEmpty()) { 1151 bool ok; 1152 double widthNumber = width.toDouble(&ok); 1153 if (!ok) 1154 return KoFilter::WrongFormat; 1155 1156 realWidthString = computeColumnWidth(widthNumber); 1157 qCDebug(lcXlsxImport) << "realWidthString:" << realWidthString; 1158 //moved saveColumnStyle(realWidthString); 1159 //! @todo hardcoded table:default-cell-style-name 1160 //moved body->addAttribute("table:default-cell-style-name", "Excel_20_Built-in_20_Normal"); 1161 } 1162 // we apparently don't need "customWidth" attr 1163 1164 TRY_READ_ATTR_WITHOUT_NS(hidden) 1165 if (!hidden.isEmpty()) { 1166 column->hidden = hidden.toInt() > 0; 1167 } 1168 1169 //moved body->endElement(); // table:table-column 1170 appendTableColumns(maxCol - minCol + 1, realWidthString); 1171 if (d->savedStyles.contains(realWidthString)) { 1172 column->styleName = d->savedStyles.value(realWidthString); 1173 } 1174 1175 m_columnCount += (maxCol - minCol); 1176 1177 if (m_columnCount > (int)MSOOXML::maximumSpreadsheetColumns()) { 1178 showWarningAboutWorksheetSize(); 1179 } 1180 1181 readNext(); 1182 READ_EPILOGUE 1183 } 1184 1185 #undef CURRENT_EL 1186 #define CURRENT_EL sheetData 1187 //! sheetData handler (Sheet Data) 1188 /*! ECMA-376, 18.3.1.80, p. 1866. 1189 This collection represents the cell table itself. This collection expresses information 1190 about each cell, grouped together by rows in the worksheet. 1191 1192 Child elements: 1193 - [done] row (Row) §18.3.1.73 1194 1195 Parent elements: 1196 - [done] worksheet (§18.3.1.99) 1197 */ 1198 KoFilter::ConversionStatus XlsxXmlWorksheetReader::read_sheetData() 1199 { 1200 READ_PROLOGUE 1201 m_currentRow = 0; 1202 while (!atEnd()) { 1203 readNext(); 1204 qCDebug(lcXlsxImport) << *this; 1205 BREAK_IF_END_OF(CURRENT_EL) 1206 if (isStartElement()) { 1207 TRY_READ_IF(row) 1208 ELSE_WRONG_FORMAT 1209 } 1210 } 1211 READ_EPILOGUE 1212 } 1213 1214 QString XlsxXmlWorksheetReader::processRowStyle(qreal height) 1215 { 1216 if (height == -1.0) { 1217 height = m_context->sheet->m_defaultRowHeight; 1218 } 1219 KoGenStyle tableRowStyle(KoGenStyle::TableRowAutoStyle, "table-row"); 1220 //! @todo alter fo:break-before? 1221 tableRowStyle.addProperty("fo:break-before", MsooXmlReader::constAuto); 1222 //! @todo alter style:use-optimal-row-height? 1223 tableRowStyle.addProperty("style:use-optimal-row-height", MsooXmlReader::constFalse); 1224 if (height >= 0.0) { 1225 tableRowStyle.addProperty("style:row-height", printCm(POINT_TO_CM(height))); 1226 } 1227 const QString currentTableRowStyleName(mainStyles->insert(tableRowStyle, "ro")); 1228 return currentTableRowStyleName; 1229 } 1230 1231 void XlsxXmlWorksheetReader::appendTableCells(int cells) 1232 { 1233 if (cells <= 0) 1234 return; 1235 body->startElement("table:table-cell"); 1236 if (cells > 1) 1237 body->addAttribute("table:number-columns-repeated", QByteArray::number(cells)); 1238 body->endElement(); // table:table-cell 1239 } 1240 1241 #undef CURRENT_EL 1242 #define CURRENT_EL row 1243 //! row handler (Row) 1244 /*! ECMA-376, 18.3.1.73, p. 1855. 1245 The element expresses information about an entire row of a worksheet, 1246 and contains all cell definitions for a particular row in the worksheet. 1247 1248 Child elements: 1249 - [done] c (Cell) §18.3.1.4 1250 - extLst (Future Feature Data Storage Area) §18.2.10 1251 1252 Parent elements: 1253 - [done] sheetData (§18.3.1.80) 1254 1255 @todo support all child elements 1256 */ 1257 KoFilter::ConversionStatus XlsxXmlWorksheetReader::read_row() 1258 { 1259 READ_PROLOGUE 1260 const QXmlStreamAttributes attrs(attributes()); 1261 TRY_READ_ATTR_WITHOUT_NS(r) 1262 //TRY_READ_ATTR_WITHOUT_NS(spans) // spans are only an optional help 1263 TRY_READ_ATTR_WITHOUT_NS(ht) 1264 //TRY_READ_ATTR_WITHOUT_NS(customHeight) not used atm 1265 TRY_READ_ATTR_WITHOUT_NS(hidden) 1266 1267 if (!r.isEmpty()) { 1268 bool ok; 1269 m_currentRow = r.toInt(&ok) - 1; 1270 if (!ok || m_currentRow < 0) 1271 return KoFilter::WrongFormat; 1272 } 1273 if (m_currentRow > (int)MSOOXML::maximumSpreadsheetRows()) { 1274 showWarningAboutWorksheetSize(); 1275 } 1276 1277 m_currentColumn = 0; 1278 Row* row = m_context->sheet->row(m_currentRow, true); 1279 if (!ht.isEmpty()) { 1280 bool ok; 1281 qreal height = ht.toDouble(&ok); 1282 if (ok) { 1283 row->styleName = processRowStyle(height); 1284 } 1285 } 1286 1287 if (!hidden.isEmpty()) { 1288 row->hidden = hidden.toInt() > 0; 1289 } 1290 1291 qreal range = (55.0/m_context->numberOfWorkSheets); 1292 int counter = 0; 1293 while (!atEnd()) { 1294 readNext(); 1295 qCDebug(lcXlsxImport) << *this; 1296 BREAK_IF_END_OF(CURRENT_EL) 1297 if (isStartElement()) { 1298 if (counter == 40) { 1299 // set the progress by the position of what was read 1300 qreal progress = 45 + range * (m_context->worksheetNumber - 1) 1301 + range * device()->pos() / device()->size(); 1302 m_context->import->reportProgress(progress); 1303 counter = 0; 1304 } 1305 ++counter; 1306 TRY_READ_IF(c) // modifies m_currentColumn 1307 SKIP_UNKNOWN 1308 } 1309 } 1310 1311 ++m_currentRow; // This row is done now. Select the next row. 1312 1313 READ_EPILOGUE 1314 } 1315 1316 //! @return true if @a v represents an integer or floating-point number 1317 static bool valueIsNumeric(const QString& v) 1318 { 1319 bool ok; 1320 v.toDouble(&ok); 1321 return ok; 1322 } 1323 1324 #undef CURRENT_EL 1325 #define CURRENT_EL c 1326 //! c handler (Cell) 1327 /*! ECMA-376, 18.3.1.4, p. 1767. 1328 This collection represents a cell in the worksheet. 1329 Information about the cell's location (reference), value, data 1330 type, formatting, and formula is expressed here. 1331 1332 Child elements: 1333 - extLst (Future Feature Data Storage Area) §18.2.10 1334 - [done] f (Formula) §18.3.1.40 1335 - is (Rich Text Inline) §18.3.1.53 1336 - [done] v (Cell Value) §18.3.1.96 1337 1338 Parent elements: 1339 - [done] row (§18.3.1.73) 1340 1341 @todo support all child elements 1342 */ 1343 KoFilter::ConversionStatus XlsxXmlWorksheetReader::read_c() 1344 { 1345 Row* row = m_context->sheet->row(m_currentRow, false); 1346 Q_ASSERT(row); 1347 Q_UNUSED(row); 1348 1349 READ_PROLOGUE 1350 const QXmlStreamAttributes attrs(attributes()); 1351 TRY_READ_ATTR_WITHOUT_NS(r) 1352 if (!r.isEmpty()) { 1353 m_currentColumn = Calligra::Sheets::Util::decodeColumnLabelText(r) - 1; 1354 if (m_currentColumn < 0) 1355 return KoFilter::WrongFormat; 1356 } 1357 1358 TRY_READ_ATTR_WITHOUT_NS(s) 1359 TRY_READ_ATTR_WITHOUT_NS(t) 1360 1361 m_value.clear(); 1362 1363 Cell* cell = m_context->sheet->cell(m_currentColumn, m_currentRow, true); 1364 1365 while (!atEnd()) { 1366 readNext(); 1367 qCDebug(lcXlsxImport) << *this; 1368 BREAK_IF_END_OF(CURRENT_EL) 1369 if (isStartElement()) { 1370 TRY_READ_IF(f) 1371 ELSE_TRY_READ_IF(v) 1372 SKIP_UNKNOWN 1373 } 1374 } 1375 1376 bool ok; 1377 uint styleId = s.toUInt(&ok); 1378 const XlsxCellFormat* cellFormat = m_context->styles->cellFormat(styleId); 1379 1380 QString formattedStyle; 1381 if (cellFormat->applyNumberFormat) 1382 formattedStyle = m_context->styles->numberFormatStyleName( cellFormat->numFmtId ); 1383 1384 //qCDebug(lcXlsxImport) << "type=" << t << "styleId=" << styleId << "applyNumberFormat=" << cellFormat->applyNumberFormat << "numberFormat=" << numberFormat << "value=" << m_value; 1385 1386 QString charStyleName; 1387 1388 // const bool addTextPElement = true;//m_value.isEmpty() || t != QLatin1String("s"); 1389 1390 if (!m_value.isEmpty()) { 1391 /* depending on type: 18.18.11 ST_CellType (Cell Type), p. 2679: 1392 b (Boolean) Cell containing a boolean. 1393 d (Date) Cell contains a date in the ISO 8601 format. 1394 e (Error) Cell containing an error. 1395 inlineStr (Inline String) Cell containing an (inline) rich string, i.e. 1396 one not in the shared string table. If this cell type is used, 1397 then the cell value is in the is element rather than the v 1398 element in the cell (c element). 1399 n (Number) Cell containing a number. 1400 s (Shared String) Cell containing a shared string. 1401 str (String) Cell containing a formula string. 1402 1403 Converting into values described in ODF1.1: "6.7.1. Variable Value Types and Values". 1404 */ 1405 1406 if (t == QLatin1String("s")) { 1407 bool ok; 1408 const int stringIndex = m_value.toInt(&ok); 1409 if (!ok || stringIndex < 0 || stringIndex >= m_context->sharedStrings->size()) { 1410 return KoFilter::WrongFormat; 1411 } 1412 QString sharedstring = m_context->sharedStrings->at(stringIndex); 1413 cell->text = sharedstring; 1414 cell->valueType = Cell::ConstString; 1415 m_value = sharedstring; 1416 // no valueAttr 1417 } else if ((t.isEmpty() && !valueIsNumeric(m_value)) || t == QLatin1String("inlineStr")) { 1418 //! @todo handle value properly 1419 cell->text = m_value; 1420 cell->valueType = Cell::ConstString; 1421 // no valueAttr 1422 } else if (t == QLatin1String("b")) { 1423 cell->text = m_value; 1424 cell->valueType = Cell::ConstBoolean; 1425 cell->valueAttr = Cell::OfficeBooleanValue; 1426 } else if (t == QLatin1String("d")) { 1427 //! @todo handle value properly 1428 cell->text = m_value; 1429 cell->valueType = Cell::ConstDate; 1430 cell->valueAttr = Cell::OfficeDateValue; 1431 } else if (t == QLatin1String("str")) { 1432 //! @todo handle value properly 1433 cell->text = m_value; 1434 cell->valueType = Cell::ConstString; 1435 // no valueAttr 1436 } else if (t == QLatin1String("n") || t.isEmpty() /* already checked if numeric */) { 1437 if (!t.isEmpty()) { // sanity check 1438 if (!valueIsNumeric(m_value)) { 1439 raiseError(i18n("Expected integer or floating point number")); 1440 return KoFilter::WrongFormat; 1441 } 1442 } 1443 const KoGenStyle* const style = mainStyles->style( formattedStyle, "" ); 1444 if( style == 0 || valueIsNumeric(m_value) ) { 1445 // body->addTextSpan(m_value); 1446 cell->valueType = Cell::ConstFloat; 1447 cell->valueAttr = Cell::OfficeValue; 1448 } else { 1449 // Tests showed that this code is never executed even when a style was set. 1450 switch( style->type() ) { 1451 case KoGenStyle::NumericDateStyle: 1452 cell->valueType = Cell::ConstDate; 1453 cell->valueAttr = Cell::OfficeDateValue; 1454 m_value = QDate( 1899, 12, 30 ).addDays( m_value.toInt() ).toString( Qt::ISODate ); 1455 break; 1456 case KoGenStyle::NumericTextStyle: 1457 cell->valueType = Cell::ConstString; 1458 cell->valueAttr = Cell::OfficeStringValue; 1459 break; 1460 default: 1461 cell->valueType = Cell::ConstFloat; 1462 cell->valueAttr = Cell::OfficeValue; 1463 break; 1464 } 1465 } 1466 } else if (t == QLatin1String("e")) { 1467 if (m_value == QLatin1String("#REF!")) 1468 cell->text = "#NAME?"; 1469 else 1470 cell->text = m_value; 1471 //! @todo full parsing needed to retrieve the type 1472 cell->valueType = Cell::ConstFloat; 1473 cell->valueAttr = Cell::OfficeValue; 1474 m_value = QLatin1String("0"); 1475 } else { 1476 raiseUnexpectedAttributeValueError(t, "c@t"); 1477 return KoFilter::WrongFormat; 1478 } 1479 } 1480 1481 // cell style 1482 if (!s.isEmpty()) { 1483 if (!ok || !cellFormat) { 1484 raiseUnexpectedAttributeValueError(s, "c@s"); 1485 return KoFilter::WrongFormat; 1486 } 1487 KoGenStyle cellStyle(KoGenStyle::TableCellAutoStyle, "table-cell"); 1488 1489 if (charStyleName.isEmpty()) { 1490 KoGenStyle* fontStyle = m_context->styles->fontStyle(cellFormat->fontId); 1491 if (!fontStyle) { 1492 qCWarning(lcXlsxImport) << "No font with ID:" << cellFormat->fontId; 1493 } else { 1494 KoGenStyle::copyPropertiesFromStyle(*fontStyle, cellStyle, KoGenStyle::TextType); 1495 } 1496 } 1497 if (!cellFormat->setupCellStyle(m_context->styles, &cellStyle)) { 1498 return KoFilter::WrongFormat; 1499 } 1500 1501 if (!formattedStyle.isEmpty()) { 1502 cellStyle.addAttribute( "style:data-style-name", formattedStyle ); 1503 } 1504 1505 if (!m_context->conditionalStyles.isEmpty()) { 1506 QString positionLetter; 1507 int positionNumber; 1508 splitToRowAndColumn(r.toLatin1().constData(), 0, r.size(), positionLetter, positionNumber); 1509 QList<QMap<QString, QString> > maps = m_context->conditionalStyleForPosition(positionLetter, positionNumber); 1510 int index = maps.size(); 1511 // Adding the lists in reversed priority order, as KoGenStyle when creating the style 1512 // adds last added first 1513 while (index > 0) { 1514 cellStyle.addStyleMap(maps.at(index - 1)); 1515 --index; 1516 } 1517 } 1518 1519 const QString cellStyleName = mainStyles->insert( cellStyle, "ce" ); 1520 cell->styleName = cellStyleName; 1521 } 1522 1523 delete cell->valueAttrValue; 1524 if (m_value.isEmpty()) { 1525 cell->valueAttrValue = 0; 1526 } else { 1527 cell->valueAttrValue = new QString(m_value); 1528 } 1529 1530 ++m_currentColumn; // This cell is done now. Select the next cell. 1531 1532 READ_EPILOGUE 1533 } 1534 1535 #undef CURRENT_EL 1536 #define CURRENT_EL f 1537 1538 //! f handler (Formula) 1539 /*! ECMA-376, 18.3.1.40, p. 1813. 1540 Formula for the cell. The formula expression is contained in the character node of this element. 1541 1542 No child elements. 1543 1544 Parent elements: 1545 - [done] c (§18.3.1.4) 1546 - nc (§18.11.1.3) 1547 - oc (§18.11.1.5) 1548 1549 @todo support all elements 1550 */ 1551 KoFilter::ConversionStatus XlsxXmlWorksheetReader::read_f() 1552 { 1553 Cell* cell = m_context->sheet->cell(m_currentColumn, m_currentRow, false); 1554 Q_ASSERT(cell); 1555 1556 READ_PROLOGUE 1557 const QXmlStreamAttributes attrs(attributes()); 1558 1559 // Range of cells which the formula applies to. Only required for shared formula, array 1560 // formula or data table. Only written on the master formula, not subsequent formula's 1561 // belonging to the same shared group, array, or data table. 1562 //TRY_READ_ATTR(ref) 1563 // Type of formula. The possible values defined by the ST_CellFormulaType (§18.18.6), p. 2677 1564 TRY_READ_ATTR(t) 1565 1566 // Shared formula groups. 1567 int sharedGroupIndex = -1; 1568 if (t == QLatin1String("shared")) { 1569 TRY_READ_ATTR(si) 1570 STRING_TO_INT(si, sharedGroupIndex, "f@si") 1571 } 1572 1573 while (!atEnd() && !hasError()) { 1574 readNext(); 1575 BREAK_IF_END_OF(CURRENT_EL) 1576 if (isCharacters()) { 1577 delete cell->formula; 1578 cell->formula = new FormulaImpl(Calligra::Sheets::MSOOXML::convertFormula(text().toString())); 1579 } 1580 } 1581 1582 if (!t.isEmpty()) { 1583 if (t == QLatin1String("shared")) { 1584 if (sharedGroupIndex >= 0) { 1585 /* Shared Group Index, p. 1815 1586 Optional attribute to optimize load performance by sharing formulas. 1587 When a formula is a shared formula (t value is shared) then this value indicates the 1588 group to which this particular cell's formula belongs. The first formula in a group of 1589 shared formulas is saved in the f element. This is considered the 'master' formula cell. 1590 Subsequent cells sharing this formula need not have the formula written in their f 1591 element. Instead, the attribute si value for a particular cell is used to figure what the 1592 formula expression should be based on the cell's relative location to the master formula 1593 cell. 1594 */ 1595 if (d->sharedFormulas.contains(sharedGroupIndex)) { 1596 if (!cell->formula /* || cell->formula->isEmpty() */) { // don't do anything if the cell already defines a formula 1597 QHash<int, Cell*>::iterator it = d->sharedFormulas.find(sharedGroupIndex); 1598 if (it != d->sharedFormulas.end()) { 1599 delete cell->formula; 1600 cell->formula = new SharedFormula(it.value()); 1601 } 1602 } 1603 } else if (cell->formula /* && !cell->formula->isEmpty()*/) { // is this cell the master cell? 1604 d->sharedFormulas[sharedGroupIndex] = cell; 1605 } 1606 } 1607 } 1608 } 1609 1610 /* 1611 if (!ref.isEmpty()) { 1612 const int pos = ref.indexOf(':'); 1613 if (pos > 0) { 1614 const QString fromCell = ref.left(pos); 1615 const QString toCell = ref.mid(pos + 1); 1616 const int c1 = Calligra::Sheets::Util::decodeColumnLabelText(fromCell) - 1; 1617 const int r1 = Calligra::Sheets::Util::decodeRowLabelText(fromCell) - 1; 1618 const int c2 = Calligra::Sheets::Util::decodeColumnLabelText(toCell) - 1; 1619 const int r2 = Calligra::Sheets::Util::decodeRowLabelText(toCell) - 1; 1620 if (c1 >= 0 && r1 >= 0 && c2 >= c1 && r2 >= r1) { 1621 for (int col = c1; col <= c2; ++col) { 1622 for (int row = r1; row <= r2; ++row) { 1623 if (col != m_currentColumn || row != m_currentRow) { 1624 if (Cell* c = m_context->sheet->cell(col, row, true)) 1625 c->formula = convertFormulaReference(cell, c); 1626 } 1627 } 1628 } 1629 } 1630 } 1631 } 1632 */ 1633 1634 READ_EPILOGUE 1635 } 1636 1637 #undef CURRENT_EL 1638 #define CURRENT_EL v 1639 //! v handler (Cell Value) 1640 /*! ECMA-376, 18.3.1.96, p. 1891. 1641 This element expresses the value contained in a cell. 1642 1643 No child elements. 1644 Parent elements: 1645 - [done] c (§18.3.1.4) 1646 - cell (§18.14.1) 1647 - nc (§18.11.1.3) 1648 - oc (§18.11.1.5) 1649 - tp (§18.15.3) 1650 1651 @todo support all parent elements 1652 */ 1653 KoFilter::ConversionStatus XlsxXmlWorksheetReader::read_v() 1654 { 1655 READ_PROLOGUE 1656 readNext(); 1657 1658 // It is possible to have empty <v/> element 1659 if (name() == "v" && isEndElement()) { 1660 READ_EPILOGUE 1661 } 1662 1663 m_value = text().toString(); 1664 m_value.replace('&', "&"); 1665 m_value.replace('<', "<"); 1666 m_value.replace('>', ">"); 1667 m_value.replace('\\', "'"); 1668 m_value.replace('"', """); 1669 1670 readNext(); 1671 READ_EPILOGUE 1672 } 1673 1674 #undef CURRENT_EL 1675 #define CURRENT_EL mergeCell 1676 /* 1677 Parent elements: 1678 - [done] mergeCells (§18.3.1.55) 1679 1680 Child elements: 1681 - none 1682 */ 1683 KoFilter::ConversionStatus XlsxXmlWorksheetReader::read_mergeCell() 1684 { 1685 READ_PROLOGUE 1686 const QXmlStreamAttributes attrs(attributes()); 1687 TRY_READ_ATTR_WITHOUT_NS(ref) 1688 QStringList refList = ref.split(':'); 1689 if (refList.count() >= 2) { 1690 const QString fromCell = refList[0]; 1691 const QString toCell = refList[1]; 1692 QRegExp rx("([A-Za-z]+)([0-9]+)"); 1693 if(rx.exactMatch(fromCell)) { 1694 const int fromRow = rx.cap(2).toInt() - 1; 1695 const int fromCol = Calligra::Sheets::Util::decodeColumnLabelText(fromCell) - 1; 1696 if(rx.exactMatch(toCell)) { 1697 Cell* cell = m_context->sheet->cell(fromCol, fromRow, true); 1698 cell->rowsMerged = rx.cap(2).toInt() - fromRow; 1699 cell->columnsMerged = Calligra::Sheets::Util::decodeColumnLabelText(toCell) - fromCol; 1700 1701 // correctly take right/bottom borders from the cells that are merged into this one 1702 const KoGenStyle* origCellStyle = mainStyles->style(cell->styleName, "table-cell"); 1703 KoGenStyle cellStyle; 1704 if (origCellStyle) { 1705 cellStyle = *origCellStyle; 1706 } 1707 qCDebug(lcXlsxImport) << cell->rowsMerged << cell->columnsMerged << cell->styleName; 1708 if (cell->rowsMerged > 1) { 1709 Cell* lastCell = m_context->sheet->cell(fromCol, fromRow + cell->rowsMerged - 1, false); 1710 qCDebug(lcXlsxImport) << lastCell; 1711 if (lastCell) { 1712 const KoGenStyle* style = mainStyles->style(lastCell->styleName, "table-cell"); 1713 qCDebug(lcXlsxImport) << lastCell->styleName; 1714 if (style) { 1715 QString val = style->property("fo:border-bottom"); 1716 qCDebug(lcXlsxImport) << val; 1717 if (!val.isEmpty()) cellStyle.addProperty("fo:border-bottom", val); 1718 val = style->property("fo:border-line-width-bottom"); 1719 if (!val.isEmpty()) cellStyle.addProperty("fo:border-line-width-bottom", val); 1720 } 1721 } 1722 } 1723 if (cell->columnsMerged > 1) { 1724 Cell* lastCell = m_context->sheet->cell(fromCol + cell->columnsMerged - 1, fromRow, false); 1725 if (lastCell) { 1726 const KoGenStyle* style = mainStyles->style(lastCell->styleName, "table-cell"); 1727 if (style) { 1728 QString val = style->property("fo:border-right"); 1729 if (!val.isEmpty()) cellStyle.addProperty("fo:border-right", val); 1730 val = style->property("fo:border-line-width-right"); 1731 if (!val.isEmpty()) cellStyle.addProperty("fo:border-line-width-right", val); 1732 } 1733 } 1734 } 1735 cell->styleName = mainStyles->insert(cellStyle, "ce"); 1736 } 1737 } 1738 } 1739 1740 readNext(); 1741 READ_EPILOGUE 1742 } 1743 1744 #undef CURRENT_EL 1745 #define CURRENT_EL mergeCells 1746 /* 1747 Parent elements: 1748 - [done] worksheet (§18.3.1.99) 1749 1750 Child elements: 1751 - mergeCell (Merged Cell) §18.3.1.54 1752 */ 1753 KoFilter::ConversionStatus XlsxXmlWorksheetReader::read_mergeCells() 1754 { 1755 READ_PROLOGUE 1756 while (!atEnd()) { 1757 readNext(); 1758 BREAK_IF_END_OF(CURRENT_EL) 1759 if (isStartElement()) { 1760 TRY_READ_IF(mergeCell) 1761 ELSE_WRONG_FORMAT 1762 } 1763 } 1764 READ_EPILOGUE 1765 } 1766 1767 #undef CURRENT_EL 1768 #define CURRENT_EL drawing 1769 1770 //! drawing handler (Drawing) 1771 /*! ECMA-376, 18.3.1.36, p.1804. 1772 1773 This element indicates that the sheet contains drawing components built 1774 on the drawingML platform. The relationship Id references the part containing 1775 the drawingML definitions. 1776 1777 Parent elements: 1778 - chartsheet (§18.3.1.12) 1779 - dialogsheet (§18.3.1.34) 1780 - [done] worksheet (§18.3.1.99) 1781 1782 Child elements - see DrawingML. 1783 */ 1784 KoFilter::ConversionStatus MSOOXML_CURRENT_CLASS::read_drawing() 1785 { 1786 READ_PROLOGUE 1787 const QXmlStreamAttributes attrs(attributes()); 1788 TRY_READ_ATTR_WITH_NS(r, id) 1789 if(!r_id.isEmpty() && !this->m_context->path.isEmpty()) { 1790 QString drawingPathAndFile = m_context->relationships->target(m_context->path, m_context->file, r_id); 1791 QString drawingPath, drawingFile; 1792 MSOOXML::Utils::splitPathAndFile(drawingPathAndFile, &drawingPath, &drawingFile); 1793 1794 XlsxXmlDrawingReaderContext context(m_context, m_context->sheet, drawingPath, drawingFile); 1795 XlsxXmlDrawingReader reader(this); 1796 const KoFilter::ConversionStatus result = m_context->import->loadAndParseDocument(&reader, drawingPathAndFile, &context); 1797 if (result != KoFilter::OK) { 1798 raiseError(reader.errorString()); 1799 return result; 1800 } 1801 1802 #if 0 //TODO 1803 if (context->m_positions.contains(XlsxDrawingObject::FromAnchor)) { 1804 XlsxDrawingObject::Position pos = context->m_positions[XlsxDrawingObject::FromAnchor]; 1805 Cell* cell = m_context->sheet->cell(pos.m_col, pos.m_row, true); 1806 cell->drawings << context; 1807 } else { 1808 delete context; 1809 } 1810 #endif 1811 } 1812 while (!atEnd()) { 1813 readNext(); 1814 BREAK_IF_END_OF(CURRENT_EL) 1815 } 1816 READ_EPILOGUE 1817 } 1818 1819 #undef CURRENT_EL 1820 #define CURRENT_EL hyperlink 1821 /* 1822 Parent elements: 1823 - [done] hyperlinks (§18.3.1.48) 1824 1825 Child elements: 1826 - none 1827 */ 1828 KoFilter::ConversionStatus XlsxXmlWorksheetReader::read_hyperlink() 1829 { 1830 READ_PROLOGUE 1831 const QXmlStreamAttributes attrs(attributes()); 1832 TRY_READ_ATTR_WITHOUT_NS(ref) 1833 TRY_READ_ATTR_WITHOUT_NS(location) 1834 TRY_READ_ATTR_WITH_NS(r, id) 1835 if (!ref.isEmpty() && (!r_id.isEmpty() || !location.isEmpty())) { 1836 const int col = Calligra::Sheets::Util::decodeColumnLabelText(ref) - 1; 1837 const int row = Calligra::Sheets::Util::decodeRowLabelText(ref) - 1; 1838 if(col >= 0 && row >= 0) { 1839 QString link = m_context->relationships->target(m_context->path, m_context->file, r_id); 1840 // it follows a hack to get right of the prepended m_context->path... 1841 if (link.startsWith(m_context->path)) 1842 link.remove(0, m_context->path.length()+1); 1843 1844 // append location 1845 if (!location.isEmpty()) link += '#' + location; 1846 1847 Cell* cell = m_context->sheet->cell(col, row, true); 1848 cell->setHyperLink( link ); 1849 } 1850 } 1851 1852 readNext(); 1853 READ_EPILOGUE 1854 } 1855 1856 #undef CURRENT_EL 1857 #define CURRENT_EL hyperlinks 1858 /* 1859 Parent elements: 1860 - [done] worksheet (§18.3.1.99) 1861 1862 Child elements: 1863 - [done] hyperlink (Hyperlink) §18.3.1.47 1864 */ 1865 KoFilter::ConversionStatus XlsxXmlWorksheetReader::read_hyperlinks() 1866 { 1867 READ_PROLOGUE 1868 while (!atEnd()) { 1869 readNext(); 1870 BREAK_IF_END_OF(CURRENT_EL) 1871 if (isStartElement()) { 1872 TRY_READ_IF(hyperlink) 1873 ELSE_WRONG_FORMAT 1874 } 1875 } 1876 READ_EPILOGUE 1877 } 1878 1879 #undef CURRENT_EL 1880 #define CURRENT_EL customFilters 1881 /* 1882 Parent elements: 1883 - [done] filterColumn (§18.3.2.7) 1884 1885 Child elements: 1886 - [done] customFilter (Custom Filter Criteria) §18.3.2.2 1887 */ 1888 KoFilter::ConversionStatus XlsxXmlWorksheetReader::read_customFilters() 1889 { 1890 READ_PROLOGUE 1891 1892 const QXmlStreamAttributes attrs(attributes()); 1893 QString andValue = attrs.value("and").toString(); 1894 1895 while (!atEnd()) { 1896 readNext(); 1897 BREAK_IF_END_OF(CURRENT_EL) 1898 if (isStartElement()) { 1899 TRY_READ_IF(customFilter) 1900 ELSE_WRONG_FORMAT 1901 } 1902 } 1903 1904 if (!m_context->autoFilters.isEmpty()) { 1905 if (andValue == "1") { 1906 m_context->autoFilters.last().type = "and"; 1907 } else { 1908 m_context->autoFilters.last().type = "or"; 1909 } 1910 } 1911 1912 READ_EPILOGUE 1913 } 1914 1915 #undef CURRENT_EL 1916 #define CURRENT_EL filters 1917 /* 1918 Parent elements: 1919 - [done] filterColumn (§18.3.2.7) 1920 1921 Child elements: 1922 - dateGroupItem (Date Grouping) §18.3.2.4 1923 - [done] filter (Filter) §18.3.2.6 1924 */ 1925 KoFilter::ConversionStatus XlsxXmlWorksheetReader::read_filters() 1926 { 1927 READ_PROLOGUE 1928 1929 const QXmlStreamAttributes attrs(attributes()); 1930 TRY_READ_ATTR_WITHOUT_NS(blank) 1931 1932 m_context->currentFilterCondition.value = "^("; 1933 1934 bool hasValueAlready = false; 1935 1936 while (!atEnd()) { 1937 readNext(); 1938 BREAK_IF_END_OF(CURRENT_EL) 1939 if (isStartElement()) { 1940 if (name() == "filter") { 1941 if (hasValueAlready) { 1942 m_context->currentFilterCondition.value += "|"; 1943 } 1944 hasValueAlready = true; 1945 TRY_READ(filter) 1946 } 1947 SKIP_UNKNOWN 1948 } 1949 } 1950 1951 m_context->currentFilterCondition.value += ")$"; 1952 m_context->currentFilterCondition.opField = "match"; 1953 1954 if (blank == "1") { 1955 m_context->currentFilterCondition.value = "0"; 1956 m_context->currentFilterCondition.opField = "empty"; 1957 } 1958 1959 if (!m_context->autoFilters.isEmpty()) { 1960 m_context->autoFilters.last().filterConditions.push_back(m_context->currentFilterCondition); 1961 } 1962 1963 READ_EPILOGUE 1964 } 1965 1966 #undef CURRENT_EL 1967 #define CURRENT_EL customFilter 1968 /* 1969 Parent elements: 1970 - [done] customFilters (§18.3.2.2) 1971 1972 Child elements: 1973 - none 1974 */ 1975 KoFilter::ConversionStatus XlsxXmlWorksheetReader::read_customFilter() 1976 { 1977 READ_PROLOGUE 1978 1979 const QXmlStreamAttributes attrs(attributes()); 1980 QString opValue = attrs.value("operator").toString(); 1981 1982 TRY_READ_ATTR_WITHOUT_NS(val) 1983 m_context->currentFilterCondition.value = val; 1984 1985 if (opValue == "notEqual") { 1986 m_context->currentFilterCondition.opField = "!="; 1987 } 1988 else { 1989 m_context->currentFilterCondition.opField = "="; 1990 } 1991 1992 if (!m_context->autoFilters.isEmpty()) { 1993 m_context->autoFilters.last().filterConditions.push_back(m_context->currentFilterCondition); 1994 } 1995 1996 readNext(); 1997 1998 READ_EPILOGUE 1999 } 2000 2001 #undef CURRENT_EL 2002 #define CURRENT_EL filter 2003 /* 2004 Parent elements: 2005 - [done] filters (§18.3.2.8) 2006 2007 Child elements: 2008 - none 2009 */ 2010 KoFilter::ConversionStatus XlsxXmlWorksheetReader::read_filter() 2011 { 2012 READ_PROLOGUE 2013 2014 const QXmlStreamAttributes attrs(attributes()); 2015 TRY_READ_ATTR_WITHOUT_NS(val) 2016 2017 m_context->currentFilterCondition.value += val; 2018 2019 readNext(); 2020 2021 READ_EPILOGUE 2022 } 2023 2024 #undef CURRENT_EL 2025 #define CURRENT_EL filterColumn 2026 /* 2027 Parent elements: 2028 - [done] autoFilter (§18.3.1.2) 2029 2030 Child elements: 2031 - colorFilter (Color Filter Criteria) §18.3.2.1 2032 - [done] customFilters (Custom Filters) §18.3.2.3 2033 - dynamicFilter (Dynamic Filter) §18.3.2.5 2034 - extLst (Future Feature Data Storage Area) §18.2.10 2035 - [done] filters (Filter Criteria) §18.3.2.8 2036 - iconFilter (Icon Filter) §18.3.2.9 2037 - top10 (Top 10) §18.3.2.10 2038 */ 2039 KoFilter::ConversionStatus XlsxXmlWorksheetReader::read_filterColumn() 2040 { 2041 READ_PROLOGUE 2042 2043 const QXmlStreamAttributes attrs(attributes()); 2044 TRY_READ_ATTR_WITHOUT_NS(colId) 2045 2046 m_context->currentFilterCondition.field = colId; 2047 2048 while (!atEnd()) { 2049 readNext(); 2050 BREAK_IF_END_OF(CURRENT_EL) 2051 if (isStartElement()) { 2052 TRY_READ_IF(filters) 2053 ELSE_TRY_READ_IF(customFilters) 2054 SKIP_UNKNOWN 2055 } 2056 } 2057 2058 READ_EPILOGUE 2059 } 2060 2061 #undef CURRENT_EL 2062 #define CURRENT_EL autoFilter 2063 /* 2064 Parent elements: 2065 - customSheetView (§18.3.1.25) 2066 - filter (§18.10.1.33) 2067 - table (§18.5.1.2) 2068 - [done] worksheet (§18.3.1.99) 2069 2070 Child elements: 2071 - extLst (Future Feature Data Storage Area) §18.2.10 2072 - [done] filterColumn (AutoFilter Column) §18.3.2.7 2073 - sortState (Sort State) §18.3.1.92 2074 */ 2075 KoFilter::ConversionStatus XlsxXmlWorksheetReader::read_autoFilter() 2076 { 2077 READ_PROLOGUE 2078 const QXmlStreamAttributes attrs(attributes()); 2079 TRY_READ_ATTR_WITHOUT_NS(ref) 2080 2081 // take last numbers and replace it with max row 2082 ref.replace(QRegExp("[0-9]+$"), QString::number(m_context->sheet->maxRow()+1)); 2083 2084 ref.prepend("."); 2085 QString sheetName = m_context->worksheetName; 2086 if (sheetName.contains('.') || sheetName.contains(' ') || sheetName.contains('\'')) { 2087 sheetName = '\'' + sheetName.replace('\'', "''") + '\''; 2088 } 2089 ref.prepend(sheetName); 2090 2091 int colon = ref.indexOf(':'); 2092 if (colon > 0) { 2093 ref.insert(colon + 1, '.'); 2094 ref.insert(colon + 1, sheetName); 2095 } 2096 2097 XlsxXmlDocumentReaderContext::AutoFilter autoFilter; 2098 autoFilter.area = ref; 2099 m_context->autoFilters.push_back(autoFilter); 2100 2101 while (!atEnd()) { 2102 readNext(); 2103 BREAK_IF_END_OF(CURRENT_EL) 2104 if (isStartElement()) { 2105 TRY_READ_IF(filterColumn) 2106 SKIP_UNKNOWN 2107 } 2108 } 2109 2110 READ_EPILOGUE 2111 } 2112 2113 #undef CURRENT_EL 2114 #define CURRENT_EL picture 2115 /* 2116 Parent elements: 2117 - chartsheet (§18.3.1.12) 2118 - [done] worksheet (§18.3.1.99) 2119 2120 Child elements: 2121 - none 2122 */ 2123 KoFilter::ConversionStatus XlsxXmlWorksheetReader::read_picture() 2124 { 2125 READ_PROLOGUE 2126 const QXmlStreamAttributes attrs(attributes()); 2127 TRY_READ_ATTR_WITH_NS(r, id) 2128 const QString link = m_context->relationships->target(m_context->path, m_context->file, r_id); 2129 QString destinationName = QLatin1String("Pictures/") + link.mid(link.lastIndexOf('/') + 1); 2130 RETURN_IF_ERROR( m_context->import->copyFile(link, destinationName, false ) ) 2131 addManifestEntryForFile(destinationName); 2132 2133 m_context->sheet->setPictureBackgroundPath(destinationName); 2134 2135 readNext(); 2136 READ_EPILOGUE 2137 } 2138 2139 #undef CURRENT_EL 2140 #define CURRENT_EL tableParts 2141 /* 2142 Parent elements: 2143 - [done] worksheet (§18.3.1.99) 2144 2145 Child elements: 2146 - [done] tablePart (Table Part) §18.3.1.94 2147 2148 */ 2149 KoFilter::ConversionStatus XlsxXmlWorksheetReader::read_tableParts() 2150 { 2151 READ_PROLOGUE 2152 while (!atEnd()) { 2153 readNext(); 2154 BREAK_IF_END_OF(CURRENT_EL) 2155 if( isStartElement() ) { 2156 TRY_READ_IF(tablePart) 2157 ELSE_WRONG_FORMAT 2158 } 2159 } 2160 READ_EPILOGUE 2161 } 2162 2163 #undef CURRENT_EL 2164 #define CURRENT_EL tablePart 2165 /* 2166 Parent elements: 2167 - [done] tableParts (§18.3.1.95) 2168 2169 Child elements: 2170 - none 2171 */ 2172 KoFilter::ConversionStatus XlsxXmlWorksheetReader::read_tablePart() 2173 { 2174 READ_PROLOGUE 2175 2176 const QXmlStreamAttributes attrs(attributes()); 2177 READ_ATTR_WITH_NS(r, id) 2178 QString tablePathAndFile = m_context->relationships->target(m_context->path, m_context->file, r_id); 2179 2180 XlsxXmlTableReaderContext context; 2181 XlsxXmlTableReader reader(this); 2182 const KoFilter::ConversionStatus result = m_context->import->loadAndParseDocument(&reader, tablePathAndFile, &context); 2183 if (result != KoFilter::OK) { 2184 raiseError(reader.errorString()); 2185 return result; 2186 } 2187 2188 readNext(); 2189 READ_EPILOGUE 2190 } 2191 2192 #undef CURRENT_EL 2193 #define CURRENT_EL legacyDrawing 2194 // todo 2195 KoFilter::ConversionStatus XlsxXmlWorksheetReader::read_legacyDrawing() 2196 { 2197 READ_PROLOGUE 2198 readNext(); 2199 READ_EPILOGUE 2200 } 2201 2202 #undef CURRENT_EL 2203 #define CURRENT_EL controls 2204 /* 2205 Parent elements: 2206 - [done] worksheet (§18.3.1.99) 2207 2208 Child elements: 2209 - [done] control (Embedded Control) §18.3.1.19 2210 */ 2211 KoFilter::ConversionStatus XlsxXmlWorksheetReader::read_controls() 2212 { 2213 READ_PROLOGUE 2214 while (!atEnd()) { 2215 readNext(); 2216 BREAK_IF_END_OF(CURRENT_EL) 2217 if( isStartElement() ) { 2218 TRY_READ_IF(control) 2219 ELSE_WRONG_FORMAT 2220 } 2221 } 2222 READ_EPILOGUE 2223 } 2224 2225 #undef CURRENT_EL 2226 #define CURRENT_EL oleObjects 2227 /* 2228 Parent elements: 2229 - [done] dialogsheet (§18.3.1.34) 2230 - [done] worksheet (§18.3.1.99) 2231 2232 Child elements: 2233 - [done] oleObject (Embedded Object) §18.3.1.59 2234 */ 2235 KoFilter::ConversionStatus XlsxXmlWorksheetReader::read_oleObjects() 2236 { 2237 READ_PROLOGUE 2238 while (!atEnd()) { 2239 readNext(); 2240 BREAK_IF_END_OF(CURRENT_EL) 2241 if( isStartElement() ) { 2242 TRY_READ_IF(oleObject) 2243 // It seems that MSO 2010 has a concept of Alternate 2244 // Content, which it throws in at unexpected times. 2245 // This is one such time. So let's try to find the 2246 // oleObject inside an mc:AlternateContent tag if possible. 2247 ELSE_TRY_READ_IF_NS(mc, AlternateContent) // Should be more specialized what we are looking for 2248 ELSE_WRONG_FORMAT 2249 } 2250 } 2251 READ_EPILOGUE 2252 } 2253 2254 #undef CURRENT_EL 2255 #define CURRENT_EL control 2256 /* 2257 Parent elements: 2258 - [done] controls (§18.3.1.21) 2259 2260 Child elements: 2261 - controlPr (Embedded Control Properties) §18.3.1.20 2262 2263 */ 2264 KoFilter::ConversionStatus XlsxXmlWorksheetReader::read_control() 2265 { 2266 READ_PROLOGUE 2267 2268 const QXmlStreamAttributes attrs(attributes()); 2269 TRY_READ_ATTR_WITHOUT_NS(shapeId) 2270 2271 // TODO: Maybe we want to do something with the actual control element. 2272 2273 // In vmldrawing, the shape identifier has also the extra chars below, therefore 2274 // we have to add them here for the match 2275 shapeId = "_x0000_s" + shapeId; 2276 2277 body->addCompleteElement(m_context->oleFrameBegins.value(shapeId).toUtf8()); 2278 body->startElement("draw:image"); 2279 body->addAttribute("xlink:href", m_context->oleReplacements.value(shapeId)); 2280 body->addAttribute("xlink:type", "simple"); 2281 body->addAttribute("xlink:show", "embed"); 2282 body->addAttribute("xlink:actuate", "onLoad"); 2283 body->endElement(); // draw:image 2284 body->addCompleteElement("</draw:frame>"); 2285 2286 while (!atEnd()) { 2287 readNext(); 2288 BREAK_IF_END_OF(CURRENT_EL) 2289 } 2290 READ_EPILOGUE 2291 } 2292 2293 #undef CURRENT_EL 2294 #define CURRENT_EL oleObject 2295 /* 2296 Parent elements: 2297 - [done] oleObjects (§18.3.1.60) 2298 2299 Child elements: 2300 - objectPr (Embedded Object Properties) §18.3.1.56 2301 2302 */ 2303 KoFilter::ConversionStatus XlsxXmlWorksheetReader::read_oleObject() 2304 { 2305 READ_PROLOGUE 2306 2307 const QXmlStreamAttributes attrs(attributes()); 2308 READ_ATTR_WITH_NS(r, id) 2309 READ_ATTR_WITHOUT_NS(progId) 2310 TRY_READ_ATTR_WITHOUT_NS(shapeId) 2311 2312 // In vmldrawing, the shape identifier has also the extra chars below, therefore 2313 // we have to add them here for the match 2314 shapeId = "_x0000_s" + shapeId; 2315 2316 const QString link = m_context->relationships->target(m_context->path, m_context->file, r_id); 2317 QString destinationName = QLatin1String("") + link.mid(link.lastIndexOf('/') + 1); 2318 KoFilter::ConversionStatus status = m_context->import->copyFile(link, destinationName, false); 2319 if (status == KoFilter::OK) { 2320 addManifestEntryForFile(destinationName); 2321 } 2322 2323 //TODO find out which cell to pick 2324 Cell* cell = m_context->sheet->cell(0, 0, true); 2325 cell->appendOleObject( qMakePair<QString,QString>(destinationName, m_context->oleReplacements.value(shapeId)), m_context->oleFrameBegins.value(shapeId)); 2326 2327 while (!atEnd()) { 2328 readNext(); 2329 BREAK_IF_END_OF(CURRENT_EL) 2330 } 2331 READ_EPILOGUE 2332 }