1 /*
2  * This file is part of Office 2007 Filters for Calligra
3  *
4  * Copyright (C) 2010 Sebastian Sauer <sebsauer@kdab.com>
5  * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies).
6  *
7  * Contact: Suresh Chande suresh.chande@nokia.com
8  *
9  * This library is free software; you can redistribute it and/or
10  * modify it under the terms of the GNU Lesser General Public License
11  * version 2.1 as published by the Free Software Foundation.
12  *
13  * This library is distributed in the hope that it will be useful, but
14  * WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16  * Lesser General Public License for more details.
17  *
18  * You should have received a copy of the GNU Lesser General Public
19  * License along with this library; if not, write to the Free Software
20  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
21  * 02110-1301 USA
22  *
23  */
24 
25 #include "XlsxXmlDrawingReader.h"
26 #include "XlsxXmlWorksheetReader.h"
27 #include "XlsxXmlWorksheetReader_p.h"
28 #include "XlsxXmlChartReader.h"
29 #include "XlsxImport.h"
30 #include "Charting.h"
31 #include "XlsxChartOdfWriter.h"
32 
33 #include <MsooXmlSchemas.h>
34 #include <MsooXmlUtils.h>
35 #include <MsooXmlRelationships.h>
36 #include <MsooXmlUnits.h>
37 #include <MsooXmlDiagramReader.h>
38 
39 #include <KoXmlWriter.h>
40 #include <KoGenStyles.h>
41 #include <KoOdfGraphicStyles.h>
42 #include <QFontMetricsF>
43 
44 #include <sheets/Util.h>
45 
46 #define MSOOXML_CURRENT_NS "xdr"
47 #define MSOOXML_CURRENT_CLASS XlsxXmlDrawingReader
48 #define BIND_READ_CLASS MSOOXML_CURRENT_CLASS
49 
50 #include <MsooXmlReader_p.h>
51 #include <MsooXmlContentTypes.h>
52 
53 // calculates the column width in pixels
columnWidth2(unsigned long col,unsigned long dx=0,qreal defaultColumnWidth=8.43)54 int columnWidth2(unsigned long col, unsigned long dx = 0, qreal defaultColumnWidth = 8.43) {
55     QFont font("Arial", 10);
56     QFontMetricsF fm(font);
57     const qreal characterWidth = fm.width("h");
58     defaultColumnWidth *= characterWidth;
59     return (defaultColumnWidth * col) + (dx / 1024.0 * defaultColumnWidth);
60 }
61 
62 // calculates the row height in pixels
rowHeight2(unsigned long row,unsigned long dy=0,qreal defaultRowHeight=12.75)63 int rowHeight2(unsigned long row, unsigned long dy = 0, qreal defaultRowHeight = 12.75)
64 {
65     return defaultRowHeight * row + dy;
66 }
67 
68 // Returns A for 0, B for 1, C for 2, etc.
columnName2(uint column)69 QString columnName2(uint column)
70 {
71     QString s;
72     unsigned digits = 1;
73     unsigned offset = 0;
74     for (unsigned limit = 26; column >= limit + offset; limit *= 26, ++digits)
75         offset += limit;
76     for (unsigned col = column - offset; digits; --digits, col /= 26)
77         s.prepend(QChar('A' + (col % 26)));
78     return s;
79 }
80 
setShape(XlsxShape * shape)81 KoXmlWriter* XlsxDrawingObject::setShape(XlsxShape* shape)
82 {
83     m_type = Shape;
84     m_shape = shape;
85 
86     delete m_shapeBody;
87     m_shapeBody = new KoXmlWriter(new QBuffer);
88     return m_shapeBody;
89 }
90 
save(KoXmlWriter * xmlWriter)91 void XlsxDrawingObject::save(KoXmlWriter* xmlWriter)
92 {
93     switch(m_type) {
94         case Unknown: {
95             // nothing to do for us
96         } break;
97         case Chart: {
98             m_chart->m_chartWriter->saveIndex(xmlWriter);
99         } break;
100         case Diagram: {
101             xmlWriter->startElement("draw:g");
102             xmlWriter->addAttribute("draw:name", "SmartArt Shapes Group");
103             xmlWriter->addAttribute("draw:z-index", "0");
104             //xmlWriter->addAttribute("table:end-cell-address", fromCellAddress());
105             //xmlWriter->addAttribute("table:end-x", rect.width());
106             //xmlWriter->addAttribute("table:end-y", rect.height());
107             m_diagram->saveIndex(xmlWriter, positionRect());
108             xmlWriter->endElement(); // draw:g
109         } break;
110         case Picture: {
111             m_picture->saveXml(xmlWriter);
112             delete m_picture;
113             m_type = Unknown;
114         } break;
115         case Shape: {
116             Q_ASSERT(m_shapeBody);
117             QByteArray data = static_cast<QBuffer*>(m_shapeBody->device())->buffer().constData();
118             xmlWriter->addCompleteElement(data);
119             delete m_shapeBody;
120             m_shapeBody = 0;
121         } break;
122     }
123 }
124 
pictureWriter()125 KoXmlWriter* XlsxDrawingObject::pictureWriter()
126 {
127     if (m_type == Unknown) {
128         setPicture(new XlsxXmlEmbeddedPicture());
129     }
130     Q_ASSERT(m_picture);
131     return m_picture->pictureWriter();
132 }
133 
134 
135 
positionRect() const136 QRect XlsxDrawingObject::positionRect() const
137 {
138     QRect rect(QPoint(0,0),QSize(0,0));
139     if(m_positions.contains(FromAnchor)) {
140         qreal defaultColumnWidth = 8.43;
141         qreal defaultRowHeight = 12.75;
142         Position f1 = m_positions[FromAnchor];
143         rect.setX( EMU_TO_POINT(f1.m_colOff) );
144         rect.setY( EMU_TO_POINT(f1.m_rowOff) );
145         if(m_positions.contains(ToAnchor)) {
146             Position f2 = m_positions[ToAnchor];
147             if(f2.m_col > 0 && f2.m_row > 0) {
148                 rect.setWidth( columnWidth2( f2.m_col - f1.m_col - 1, EMU_TO_POINT(f2.m_colOff), defaultColumnWidth) );
149                 rect.setHeight( rowHeight2( f2.m_row - f1.m_row - 1, EMU_TO_POINT(f2.m_rowOff), defaultRowHeight) );
150             }
151         }
152         Q_ASSERT(rect.x() >= 0);
153         Q_ASSERT(rect.y() >= 0);
154         Q_ASSERT(rect.width() >= 0);
155         Q_ASSERT(rect.height() >= 0);
156     }
157     return rect;
158 }
159 
cellAddress(const QString & sheetname,int row,int column) const160 QString XlsxDrawingObject::cellAddress(const QString &sheetname, int row, int column) const
161 {
162     QString result;
163     if(!sheetname.isEmpty())
164         result += sheetname + '.';
165     result += columnName2(column) + QString::number(row + 1);
166     return result;
167 }
168 
fromCellAddress() const169 QString XlsxDrawingObject::fromCellAddress() const
170 {
171     if(!m_positions.contains(FromAnchor)) return QString();
172     Position f = m_positions[FromAnchor];
173     return cellAddress(m_sheet->m_name, f.m_row, f.m_col);
174 }
175 
toCellAddress() const176 QString XlsxDrawingObject::toCellAddress() const
177 {
178     if(!m_positions.contains(ToAnchor)) return QString();
179     Position f = m_positions[ToAnchor];
180     return cellAddress(m_sheet->m_name, f.m_row, f.m_col);
181 }
182 
isAnchoredToCell() const183 bool XlsxDrawingObject::isAnchoredToCell() const
184 {
185     return (m_positions.contains(FromAnchor));
186 }
187 
188 
XlsxXmlDrawingReaderContext(XlsxXmlWorksheetReaderContext * _worksheetReaderContext,Sheet * _sheet,const QString & _path,const QString & _file)189 XlsxXmlDrawingReaderContext::XlsxXmlDrawingReaderContext(XlsxXmlWorksheetReaderContext* _worksheetReaderContext, Sheet* _sheet, const QString& _path, const QString& _file)
190     : MSOOXML::MsooXmlReaderContext(_worksheetReaderContext->relationships)
191     , import(_worksheetReaderContext->import)
192     , path(_path)
193     , file(_file)
194     , themes((_worksheetReaderContext->themes))
195     , worksheetReaderContext(_worksheetReaderContext)
196     , sheet(_sheet)
197     , m_groupDepthCounter(0)
198 {
199 }
200 
~XlsxXmlDrawingReaderContext()201 XlsxXmlDrawingReaderContext::~XlsxXmlDrawingReaderContext()
202 {
203 }
204 
XlsxXmlDrawingReader(KoOdfWriters * writers)205 XlsxXmlDrawingReader::XlsxXmlDrawingReader(KoOdfWriters *writers)
206     : MSOOXML::MsooXmlCommonReader(writers)
207     , m_context(0)
208     , m_currentDrawingObject(0)
209     , m_anchorType(XlsxDrawingObject::NoAnchor)
210     , m_chartNumber(0)
211 {
212     initDrawingML();
213 }
214 
~XlsxXmlDrawingReader()215 XlsxXmlDrawingReader::~XlsxXmlDrawingReader()
216 {
217     Q_ASSERT(!m_currentDrawingObject);
218 }
219 
read(MSOOXML::MsooXmlReaderContext * context)220 KoFilter::ConversionStatus XlsxXmlDrawingReader::read(MSOOXML::MsooXmlReaderContext* context)
221 {
222     m_context = dynamic_cast<XlsxXmlDrawingReaderContext*>(context);
223     Q_ASSERT(m_context);
224 
225     readNext();
226     if (!isStartDocument()) {
227         return KoFilter::WrongFormat;
228     }
229 
230     readNext();
231     if (!expectEl("xdr:wsDr")) {
232         return KoFilter::WrongFormat;
233     }
234 
235     while (!atEnd()) {
236         readNext();
237         if (isEndElement() && name() == "wsDr") {
238             break;
239         }
240         if (isStartElement()) {
241             TRY_READ_IF(oneCellAnchor)
242             ELSE_TRY_READ_IF(twoCellAnchor)
243             ELSE_TRY_READ_IF(absoluteAnchor)
244             SKIP_UNKNOWN
245         }
246     }
247 #if 0
248     m_context->saveCurrentCellData();
249 #endif
250     m_context = 0;
251     return KoFilter::OK;
252 }
253 
254 #undef CURRENT_EL
255 #define CURRENT_EL twoCellAnchor
read_twoCellAnchor()256 KoFilter::ConversionStatus XlsxXmlDrawingReader::read_twoCellAnchor()
257 {
258     READ_PROLOGUE
259 
260     return read_anchor("twoCellAnchor");
261 
262     READ_EPILOGUE
263 }
264 
265 #undef CURRENT_EL
266 #define CURRENT_EL oneCellAnchor
read_oneCellAnchor()267 KoFilter::ConversionStatus XlsxXmlDrawingReader::read_oneCellAnchor()
268 {
269     READ_PROLOGUE
270 
271     return read_anchor("oneCellAnchor");
272 
273     READ_EPILOGUE
274 }
275 
276 #undef CURRENT_EL
277 #define CURRENT_EL absoluteAnchor
read_absoluteAnchor()278 KoFilter::ConversionStatus XlsxXmlDrawingReader::read_absoluteAnchor()
279 {
280     READ_PROLOGUE
281 
282     return read_anchor("absoluteAnchor");
283 
284     READ_EPILOGUE
285 }
286 
read_anchor(const QString & reference)287 KoFilter::ConversionStatus XlsxXmlDrawingReader::read_anchor(const QString& reference)
288 {
289     class DrawingObjectGuard { // like QScopedPointer but sets the pointer to NULL afterwards
290         public:
291             DrawingObjectGuard(XlsxDrawingObject** obj) : m_obj(obj) {}
292             ~DrawingObjectGuard() { delete *m_obj; *m_obj = 0; }
293         private:
294             XlsxDrawingObject** m_obj;
295     };
296 
297     Q_ASSERT(!m_currentDrawingObject);
298     m_currentDrawingObject = new XlsxDrawingObject(m_context->sheet);
299     DrawingObjectGuard _guard(&m_currentDrawingObject);
300 
301     while (!atEnd()) {
302         readNext();
303         if (isEndElement() && name() == reference) {
304             break;
305         }
306         if (isStartElement()) {
307             // twoCellAnchor does define the 'from' and 'to' elements which do define the anchor-points
308             TRY_READ_IF(from)
309             ELSE_TRY_READ_IF(to)
310             else if (qualifiedName() == QLatin1String("xdr:ext")) {
311                 // read_ext expects the qualifed name a:ext
312                 const QXmlStreamAttributes attrs(attributes());
313 
314                 READ_ATTR_WITHOUT_NS(cx)
315                 STRING_TO_INT(cx, m_svgWidth, "ext@cx")
316                 READ_ATTR_WITHOUT_NS(cy)
317                 STRING_TO_INT(cy, m_svgHeight, "ext@cy")
318                 readNext();
319             }
320             ELSE_TRY_READ_IF(ext)
321             // a shape
322             ELSE_TRY_READ_IF(sp)
323             // the reference to a picture
324             ELSE_TRY_READ_IF(pic)
325             // a graphic-frame
326             ELSE_TRY_READ_IF(graphicFrame)
327             ELSE_TRY_READ_IF(cxnSp)
328             ELSE_TRY_READ_IF(grpSp)
329             SKIP_UNKNOWN
330         }
331     }
332 
333     if (m_currentDrawingObject->m_type != XlsxDrawingObject::Unknown) {
334         if (m_currentDrawingObject->isAnchoredToCell()) {
335             XlsxDrawingObject::Position pos = m_currentDrawingObject->m_positions[XlsxDrawingObject::FromAnchor];
336             Cell* cell = m_context->sheet->cell(pos.m_col, pos.m_row, true);
337             cell->appendDrawing(m_currentDrawingObject);
338             m_currentDrawingObject = 0;
339         }
340     }
341     return KoFilter::OK;
342 }
343 
344 #undef CURRENT_EL
345 #define CURRENT_EL from
read_from()346 KoFilter::ConversionStatus XlsxXmlDrawingReader::read_from()
347 {
348     READ_PROLOGUE
349 #if 0
350     m_context->saveCurrentCellData();
351 #endif
352     m_anchorType = XlsxDrawingObject::FromAnchor;
353     while (!atEnd()) {
354         readNext();
355         BREAK_IF_END_OF(CURRENT_EL)
356         if (isStartElement()) {
357             TRY_READ_IF(col)
358             ELSE_TRY_READ_IF(row)
359             ELSE_TRY_READ_IF(colOff)
360             ELSE_TRY_READ_IF(rowOff)
361         }
362     }
363     m_anchorType = XlsxDrawingObject::NoAnchor;
364     READ_EPILOGUE
365 }
366 
367 #undef CURRENT_EL
368 #define CURRENT_EL to
read_to()369 KoFilter::ConversionStatus XlsxXmlDrawingReader::read_to()
370 {
371     READ_PROLOGUE
372     m_anchorType = XlsxDrawingObject::ToAnchor;
373     while (!atEnd()) {
374         readNext();
375         BREAK_IF_END_OF(CURRENT_EL)
376         if (isStartElement()) {
377             TRY_READ_IF(col)
378             ELSE_TRY_READ_IF(row)
379             ELSE_TRY_READ_IF(colOff)
380             ELSE_TRY_READ_IF(rowOff)
381         }
382     }
383     m_anchorType = XlsxDrawingObject::NoAnchor;
384     READ_EPILOGUE
385 }
386 
387 #undef CURRENT_EL
388 #define CURRENT_EL col
read_col()389 KoFilter::ConversionStatus XlsxXmlDrawingReader::read_col()
390 {
391     m_currentDrawingObject->m_positions[m_anchorType].m_col = readElementText().toInt(); // default value is zero
392     return KoFilter::OK;
393 }
394 
395 #undef CURRENT_EL
396 #define CURRENT_EL row
read_row()397 KoFilter::ConversionStatus XlsxXmlDrawingReader::read_row()
398 {
399     m_currentDrawingObject->m_positions[m_anchorType].m_row = readElementText().toInt(); // default value is zero
400     return KoFilter::OK;
401 }
402 
403 #undef CURRENT_EL
404 #define CURRENT_EL colOff
read_colOff()405 KoFilter::ConversionStatus XlsxXmlDrawingReader::read_colOff()
406 {
407     m_currentDrawingObject->m_positions[m_anchorType].m_colOff = readElementText().toInt(); // default value is zero
408     return KoFilter::OK;
409 }
410 
411 #undef CURRENT_EL
412 #define CURRENT_EL rowOff
read_rowOff()413 KoFilter::ConversionStatus XlsxXmlDrawingReader::read_rowOff()
414 {
415     m_currentDrawingObject->m_positions[m_anchorType].m_rowOff = readElementText().toInt(); // default value is zero
416     return KoFilter::OK;
417 }
418 
419 #undef CURRENT_EL
420 #define CURRENT_EL graphicFrame
421 //! graphicFrame (Graphic Frame)
422 /*! ECMA-376, 20.5.2.16, p. 3524 (SpreadSheetML)
423   This element describes a single graphical object frame for a spreadsheet
424   which contains a graphical object.
425 
426   Parent Elements:
427   - [done] absoluteAnchor (§20.5.2.1)
428   - [done] grpSp (§20.5.2.17)
429   - [done] oneCellAnchor (§20.5.2.24)
430   - [done] twoCellAnchor (§20.5.2.33)
431 
432   Child Elements:
433   - [done] graphic (Graphic Object) (§20.1.2.2.16)
434   - nvGraphicFramePr (Non-Visual Properties for a Graphic Frame) (§20.5.2.20)
435   - xfrm (2D Transform for Graphic Frame) (§20.5.2.36)
436 */
read_graphicFrame()437 KoFilter::ConversionStatus XlsxXmlDrawingReader::read_graphicFrame()
438 {
439     READ_PROLOGUE
440 
441     //TODO: Create a graphic style for the frame.
442 
443     MSOOXML::Utils::XmlWriteBuffer buffer;
444     body = buffer.setWriter(body);
445 
446     while (!atEnd()) {
447         readNext();
448         BREAK_IF_END_OF(CURRENT_EL)
449         if (isStartElement()) {
450             TRY_READ_IF_NS(a, graphic)
451             SKIP_UNKNOWN
452         }
453     }
454 
455     body = buffer.originalWriter();
456 
457     if (m_context->graphicObjectIsGroup) {
458         body->startElement("draw:g");
459     } else {
460         body->startElement("draw:frame");
461     }
462 
463     buffer.releaseWriter();
464     body->endElement(); //draw:g/draw:frame
465 
466     READ_EPILOGUE
467 }
468 
XlsxXmlEmbeddedPicture()469 XlsxXmlEmbeddedPicture::XlsxXmlEmbeddedPicture():m_pictureWriter(0)
470 {
471     m_pictureBuffer.open(QIODevice::ReadWrite);
472 }
473 
~XlsxXmlEmbeddedPicture()474 XlsxXmlEmbeddedPicture::~XlsxXmlEmbeddedPicture()
475 {
476     delete m_pictureWriter;
477 }
478 
479 
pictureWriter()480 KoXmlWriter* XlsxXmlEmbeddedPicture::pictureWriter()
481 {
482     if (!m_pictureWriter) {
483         m_pictureWriter = new KoXmlWriter(&m_pictureBuffer);
484     }
485     return m_pictureWriter;
486 }
487 
488 
saveXml(KoXmlWriter * xmlWriter)489 bool XlsxXmlEmbeddedPicture::saveXml(KoXmlWriter *xmlWriter)   // save all needed attributes to .ods
490 {
491     Q_ASSERT(m_pictureWriter);
492     if (!m_pictureWriter || !m_pictureWriter->device()->size()){
493         return false;
494     }
495 
496     xmlWriter->addCompleteElement(m_pictureWriter->device());
497     return true;
498 }
499 
500 
501 #undef MSOOXML_CURRENT_NS
502 #define MSOOXML_CURRENT_NS "a" // required for the next header file
503 
504 // in PPTX we do not have pPr, so p@text:style-name should be added earlier
505 //#define SETUP_PARA_STYLE_IN_READ_P
506 #include <MsooXmlCommonReaderImpl.h> // adds a:p, a:pPr, a:t, a:r, etc.
507 
508 #define DRAWINGML_NS "a"
509 #define DRAWINGML_PIC_NS "xdr" // DrawingML/Picture
510 #define DRAWINGML_TXBODY_NS "xdr" // DrawingML/Picture
511 #define XLSXXMLDRAWINGREADER_CPP
512 
513 #include <MsooXmlCommonReaderDrawingMLImpl.h> // adds p:pic, etc.
514 //#include <MsooXmlDrawingReaderTableImpl.h>  // adds a:tbl
515