1 /*
2  * This file is part of Office 2007 Filters for Calligra
3  * Copyright (C) 2002 Laurent Montel <lmontel@mandrakesoft.com>
4  * Copyright (c) 2003 Lukas Tinkl <lukas@kde.org>
5  * Copyright (C) 2003 David Faure <faure@kde.org>
6  * Copyright (C) 2009-2010 Nokia Corporation and/or its subsidiary(-ies).
7  * Contact: Suresh Chande suresh.chande@nokia.com
8  * Copyright (C) 2011 Matus Uzak <matus.uzak@ixonos.com>
9  *
10  * Utils::columnName() based on Cell::columnName() from calligra/kspread/Utils.cpp:
11  * Copyright 2006-2007 Stefan Nikolaus <stefan.nikolaus@kdemail.net>
12  * Copyright 2004 Tomas Mecir <mecirt@gmail.com>
13  * Copyright 1999-2002,2004 Laurent Montel <montel@kde.org>
14  * Copyright 2002,2004 Ariya Hidayat <ariya@kde.org>
15  * Copyright 2002-2003 Norbert Andres <nandres@web.de>
16  * Copyright 2003 Stefan Hetzl <shetzl@chello.at>
17  * Copyright 2001-2002 Philipp Mueller <philipp.mueller@gmx.de>
18  * Copyright 2002 Harri Porten <porten@kde.org>
19  * Copyright 2002 John Dailey <dailey@vt.edu>
20  * Copyright 1999-2001 David Faure <faure@kde.org>
21  * Copyright 2000-2001 Werner Trobin <trobin@kde.org>
22  * Copyright 2000 Simon Hausmann <hausmann@kde.org
23  * Copyright 1998-1999 Torben Weis <weis@kde.org>
24  * Copyright 1999 Michael Reiher <michael.reiher@gmx.de>
25  * Copyright 1999 Reginald Stadlbauer <reggie@kde.org>
26  *
27  * This library is free software; you can redistribute it and/or
28  * modify it under the terms of the GNU Lesser General Public License
29  * version 2.1 as published by the Free Software Foundation.
30  *
31  * This library is distributed in the hope that it will be useful, but
32  * WITHOUT ANY WARRANTY; without even the implied warranty of
33  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
34  * Lesser General Public License for more details.
35  *
36  * You should have received a copy of the GNU Lesser General Public
37  * License along with this library; if not, write to the Free Software
38  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
39  * 02110-1301 USA
40  *
41  */
42 
43 #include "MsooXmlUtils.h"
44 #include "MsooXmlUnits.h"
45 #include "MsooXmlContentTypes.h"
46 #include "MsooXmlSchemas.h"
47 #include "MsooXmlReader.h"
48 #include "MsooXmlDebug.h"
49 
50 #include "ooxml_pole.h"
51 
52 #include <styles/KoCharacterStyle.h>
53 #include <KoXmlReader.h>
54 #include <KoXmlWriter.h>
55 #include <KoGenStyles.h>
56 #include <KoUnit.h>
57 
58 #include <klocalizedstring.h>
59 #include <kzip.h>
60 
61 #include <QGlobalStatic>
62 #include <QDomDocument>
63 #include <QColor>
64 #include <QBrush>
65 #include <QImage>
66 #include <QImageReader>
67 #include <QPalette>
68 #include <QRegExp>
69 
70 
71 #include <memory>
72 
73 // common officedocument content types
74 const char MSOOXML::ContentTypes::coreProps[] =            "application/vnd.openxmlformats-package.core-properties+xml";
75 const char MSOOXML::ContentTypes::extProps[] =             "application/vnd.openxmlformats-officedocument.extended-properties+xml";
76 const char MSOOXML::ContentTypes::theme[] =                "application/vnd.openxmlformats-officedocument.theme+xml";
77 
78 // wordprocessingml-specific content types
79 const char MSOOXML::ContentTypes::wordDocument[] =         "application/vnd.openxmlformats-officedocument.wordprocessingml.document.main+xml";
80 const char MSOOXML::ContentTypes::wordSettings[] =         "application/vnd.openxmlformats-officedocument.wordprocessingml.settings+xml";
81 const char MSOOXML::ContentTypes::wordStyles[] =           "application/vnd.openxmlformats-officedocument.wordprocessingml.styles+xml";
82 const char MSOOXML::ContentTypes::wordHeader[] =           "application/vnd.openxmlformats-officedocument.wordprocessingml.header+xml";
83 const char MSOOXML::ContentTypes::wordFooter[] =           "application/vnd.openxmlformats-officedocument.wordprocessingml.footer+xml";
84 const char MSOOXML::ContentTypes::wordFootnotes[] =        "application/vnd.openxmlformats-officedocument.wordprocessingml.footnotes+xml";
85 const char MSOOXML::ContentTypes::wordEndnotes[] =         "application/vnd.openxmlformats-officedocument.wordprocessingml.endnotes+xml";
86 const char MSOOXML::ContentTypes::wordFontTable[] =        "application/vnd.openxmlformats-officedocument.wordprocessingml.fontTable+xml";
87 const char MSOOXML::ContentTypes::wordWebSettings[] =      "application/vnd.openxmlformats-officedocument.wordprocessingml.webSettings+xml";
88 const char MSOOXML::ContentTypes::wordTemplate[] =         "application/vnd.openxmlformats-officedocument.wordprocessingml.template.main+xml";
89 const char MSOOXML::ContentTypes::wordComments[] =         "application/vnd.openxmlformats-officedocument.wordprocessingml.comments+xml";
90 
91 // presentationml-specific content types
92 const char MSOOXML::ContentTypes::presentationDocument[] =      "application/vnd.openxmlformats-officedocument.presentationml.presentation.main+xml";
93 const char MSOOXML::ContentTypes::presentationSlide[] =         "application/vnd.openxmlformats-officedocument.presentationml.slide+xml";
94 const char MSOOXML::ContentTypes::presentationSlideLayout[] =   "application/vnd.openxmlformats-officedocument.presentationml.slideLayout+xml";
95 const char MSOOXML::ContentTypes::presentationSlideShow[] =     "application/vnd.openxmlformats-officedocument.presentationml.slideshow.main+xml";
96 const char MSOOXML::ContentTypes::presentationTemplate[] =      "application/vnd.openxmlformats-officedocument.presentationml.template.main+xml";
97 const char MSOOXML::ContentTypes::presentationNotes[] =         "application/vnd.openxmlformats-officedocument.presentationml.notesMaster+xml";
98 const char MSOOXML::ContentTypes::presentationTableStyles[] =   "application/vnd.openxmlformats-officedocument.presentationml.tableStyles+xml";
99 const char MSOOXML::ContentTypes::presentationProps[] =         "application/vnd.openxmlformats-officedocument.presentationml.presProps+xml";
100 const char MSOOXML::ContentTypes::presentationViewProps[] =     "application/vnd.openxmlformats-officedocument.presentationml.viewProps+xml";
101 const char MSOOXML::ContentTypes::presentationComments[] =      "application/vnd.openxmlformats-officedocument.presentationml.comments+xml";
102 
103 // spreadsheetml-specific content types
104 const char MSOOXML::ContentTypes::spreadsheetDocument[] =        "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml";
105 const char MSOOXML::ContentTypes::spreadsheetMacroDocument[] =   "application/vnd.ms-excel.sheet.macroEnabled.main+xml";
106 const char MSOOXML::ContentTypes::spreadsheetPrinterSettings[] = "application/vnd.openxmlformats-officedocument.spreadsheetml.printerSettings";
107 const char MSOOXML::ContentTypes::spreadsheetStyles[] =          "application/vnd.openxmlformats-officedocument.spreadsheetml.styles+xml";
108 const char MSOOXML::ContentTypes::spreadsheetWorksheet[] =       "application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml";
109 const char MSOOXML::ContentTypes::spreadsheetCalcChain[] =       "application/vnd.openxmlformats-officedocument.spreadsheetml.calcChain+xml";
110 const char MSOOXML::ContentTypes::spreadsheetSharedStrings[] =   "application/vnd.openxmlformats-officedocument.spreadsheetml.sharedStrings+xml";
111 const char MSOOXML::ContentTypes::spreadsheetTemplate[] =        "application/vnd.openxmlformats-officedocument.spreadsheetml.template.main+xml";
112 const char MSOOXML::ContentTypes::spreadsheetComments[] =        "application/vnd.openxmlformats-officedocument.spreadsheetml.comments+xml";
113 
114 //generic namespaces
115 const char MSOOXML::Schemas::dublin_core[] =                                "http://purl.org/dc/elements/1.1/";
116 
117 // common namespaces
118 const char MSOOXML::Schemas::contentTypes[] =                               "http://schemas.openxmlformats.org/package/2006/content-types";
119 
120 const char MSOOXML::Schemas::relationships[] =                              "http://schemas.openxmlformats.org/package/2006/relationships";
121 const char MSOOXML::Schemas::core_properties[] =                            "http://schemas.openxmlformats.org/package/2006/metadata/core-properties";
122 
123 // ISO/IEC 29500-1:2008(E), Annex A. (normative), p. 4355
124 // See also: specs/all.xsd
125 // A.1 WordprocessingML
126 const char MSOOXML::Schemas::wordprocessingml[] =                           "http://schemas.openxmlformats.org/wordprocessingml/2006/main";
127 
128 // A.2 SpreadsheetML
129 const char MSOOXML::Schemas::spreadsheetml[] =                              "http://schemas.openxmlformats.org/spreadsheetml/2006/main";
130 
131 // A.3 PresentationML
132 const char MSOOXML::Schemas::presentationml[] =                             "http://schemas.openxmlformats.org/presentationml/2006/main";
133 
134 // A.4 DrawingML - Framework
135 const char MSOOXML::Schemas::drawingml::main[] =                            "http://schemas.openxmlformats.org/drawingml/2006/main";
136 const char MSOOXML::Schemas::drawingml::wordprocessingDrawing[] =           "http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing";
137 const char MSOOXML::Schemas::drawingml::spreadsheetDrawing[] =              "http://schemas.openxmlformats.org/drawingml/2006/spreadsheetDrawing";
138 const char MSOOXML::Schemas::drawingml::compatibility[] =                   "http://schemas.openxmlformats.org/drawingml/2006/compatibility";
139 const char MSOOXML::Schemas::drawingml::lockedCanvas[] =                    "http://schemas.openxmlformats.org/drawingml/2006/lockedCanvas";
140 const char MSOOXML::Schemas::drawingml::picture[] =                         "http://schemas.openxmlformats.org/drawingml/2006/picture";
141 
142 // A.5 DrawingML - Components
143 const char MSOOXML::Schemas::drawingml::chart[] =                           "http://schemas.openxmlformats.org/drawingml/2006/chart";
144 const char MSOOXML::Schemas::drawingml::chartDrawing[] =                    "http://schemas.openxmlformats.org/drawingml/2006/chartDrawing";
145 const char MSOOXML::Schemas::drawingml::diagram[] =                         "http://schemas.openxmlformats.org/drawingml/2006/diagram";
146 
147 // A.6 Shared MLs
148 const char MSOOXML::Schemas::officeDocument::math[] =                       "http://schemas.openxmlformats.org/officeDocument/2006/math";
149 const char MSOOXML::Schemas::officeDocument::bibliography[] =               "http://schemas.openxmlformats.org/officeDocument/2006/bibliography";
150 const char MSOOXML::Schemas::officeDocument::characteristics[] =            "http://schemas.openxmlformats.org/officeDocument/2006/characteristics";
151 const char MSOOXML::Schemas::officeDocument::customXml[] =                  "http://schemas.openxmlformats.org/officeDocument/2006/customXml";
152 const char MSOOXML::Schemas::officeDocument::custom_properties[] =          "http://schemas.openxmlformats.org/officeDocument/2006/custom-properties";
153 const char MSOOXML::Schemas::officeDocument::docPropsVTypes[] =             "http://schemas.openxmlformats.org/officeDocument/2006/docPropsVTypes";
154 const char MSOOXML::Schemas::officeDocument::extended_properties[] =        "http://schemas.openxmlformats.org/officeDocument/2006/extended-properties";
155 const char MSOOXML::Schemas::officeDocument::relationships[] =              "http://schemas.openxmlformats.org/officeDocument/2006/relationships";
156 const char MSOOXML::Schemas::officeDocument::sharedTypes[] =                "http://schemas.openxmlformats.org/officeDocument/2006/sharedTypes";
157 
158 // A.7 Custom XML Schema References
159 const char MSOOXML::Schemas::schemaLibrary[] =                              "http://schemas.openxmlformats.org/schemaLibrary/2006/main";
160 
161 // Marks that the value has not been modified;
162 static const char UNUSED[] = "UNUSED";
163 
164 using namespace MSOOXML;
165 
166 //-----------------------------------------
167 
loadAndParse(QIODevice * io,KoXmlDocument & doc,QString & errorMessage,const QString & fileName)168 KoFilter::ConversionStatus Utils::loadAndParse(QIODevice* io, KoXmlDocument& doc,
169         QString& errorMessage, const QString & fileName)
170 {
171     errorMessage.clear();
172 
173     QString errorMsg;
174     int errorLine, errorColumn;
175     bool ok = doc.setContent(io, true, &errorMsg, &errorLine, &errorColumn);
176     if (!ok) {
177         errorMsooXml << "Parsing error in " << fileName << ", aborting!" << endl
178         << " In line: " << errorLine << ", column: " << errorColumn << endl
179         << " Error message: " << errorMsg;
180         errorMessage = i18n("Parsing error in the main document at line %1, column %2.\nError message: %3", errorLine , errorColumn , errorMsg);
181         return KoFilter::ParsingError;
182     }
183     debugMsooXml << "File" << fileName << "loaded and parsed.";
184     return KoFilter::OK;
185 }
186 
loadAndParse(KoXmlDocument & doc,const KZip * zip,QString & errorMessage,const QString & fileName)187 KoFilter::ConversionStatus Utils::loadAndParse(KoXmlDocument& doc, const KZip* zip,
188         QString& errorMessage, const QString& fileName)
189 {
190     errorMessage.clear();
191     KoFilter::ConversionStatus status;
192     std::auto_ptr<QIODevice> device(openDeviceForFile(zip, errorMessage, fileName, status));
193     if (!device.get())
194         return status;
195     return loadAndParse(device.get(), doc, errorMessage, fileName);
196 }
197 
loadAndParseDocument(MsooXmlReader * reader,const KZip * zip,KoOdfWriters * writers,QString & errorMessage,const QString & fileName,MsooXmlReaderContext * context)198 KoFilter::ConversionStatus Utils::loadAndParseDocument(MsooXmlReader* reader,
199         const KZip* zip,
200         KoOdfWriters *writers,
201         QString& errorMessage,
202         const QString& fileName,
203         MsooXmlReaderContext* context)
204 {
205     Q_UNUSED(writers)
206     errorMessage.clear();
207     KoFilter::ConversionStatus status;
208     std::auto_ptr<QIODevice> device(openDeviceForFile(zip, errorMessage, fileName, status));
209     if (!device.get())
210         return status;
211     reader->setDevice(device.get());
212     reader->setFileName(fileName); // for error reporting
213     status = reader->read(context);
214     if (status != KoFilter::OK) {
215         errorMessage = reader->errorString();
216         return status;
217     }
218     debugMsooXml << "File" << fileName << "loaded and parsed.";
219     return KoFilter::OK;
220 }
221 
openDeviceForFile(const KZip * zip,QString & errorMessage,const QString & fileName,KoFilter::ConversionStatus & status)222 QIODevice* Utils::openDeviceForFile(const KZip* zip, QString& errorMessage, const QString& fileName,
223                                     KoFilter::ConversionStatus& status)
224 {
225     debugMsooXml << "Trying to open" << fileName;
226     errorMessage.clear();
227     const KArchiveEntry* entry = zip->directory()->entry(fileName);
228     if (!entry) {
229         errorMessage = i18n("Entry '%1' not found.", fileName);
230         debugMsooXml << errorMessage;
231         status = KoFilter::FileNotFound;
232         return 0;
233     }
234     if (!entry->isFile()) {
235         errorMessage = i18n("Entry '%1' is not a file.", fileName);
236         debugMsooXml << errorMessage;
237         status = KoFilter::WrongFormat;
238         return 0;
239     }
240     const KZipFileEntry* f = static_cast<const KZipFileEntry *>(entry);
241     debugMsooXml << "Entry" << fileName << "has size" << f->size();
242     status = KoFilter::OK;
243     // There seem to be some problems with kde/zlib when trying to read
244     // multiple streams, this functionality is needed in the filter
245     // Until there's another solution for this, this avoids the problem
246     //return f->createDevice();
247     QBuffer *device = new QBuffer();
248     device->setData(f->data());
249     device->open(QIODevice::ReadOnly);
250     return device;
251 }
252 
253 #define BLOCK_SIZE 4096
copyOle(QString & errorMessage,const QString sourceName,KoStore * outputStore,const QString & destinationName,const KZip * zip)254 static KoFilter::ConversionStatus copyOle(QString& errorMessage,
255                                     const QString sourceName, KoStore *outputStore,
256                                     const QString& destinationName, const KZip* zip)
257 {
258     KoFilter::ConversionStatus status = KoFilter::OK;
259 
260     QIODevice* inputDevice = Utils::openDeviceForFile(zip, errorMessage, sourceName, status);
261     if (!inputDevice) {
262         // Source did not exist
263         return KoFilter::CreationError;
264     }
265     inputDevice->open(QIODevice::ReadOnly);
266 
267     OOXML_POLE::Storage storage(inputDevice);
268     if (!storage.open()) {
269         debugMsooXml << "Cannot open " << sourceName;
270         return KoFilter::WrongFormat;
271     }
272 
273     std::list<std::string> lista = storage.entries();
274     std::string oleType = "Contents";
275 
276     for (std::list<std::string>::iterator it = lista.begin(); it != lista.end(); ++it)  {
277         //debugMsooXml << "ENTRY " << (*it).c_str();
278         if (QString((*it).c_str()).contains("Ole10Native")) {
279             oleType = "Ole10Native";
280         }
281         else if (QString((*it).c_str()).contains("CONTENTS")) {
282             oleType = "CONTENTS";
283         }
284     }
285 
286     OOXML_POLE::Stream stream(&storage, oleType);
287     QByteArray array;
288     array.resize(stream.size());
289 
290     unsigned long r = stream.read((unsigned char*)array.data(), stream.size());
291     if (r != stream.size()) {
292         errorMsooXml << "Error while reading from stream";
293         return KoFilter::WrongFormat;
294     }
295 
296     if (oleType == "Contents" || oleType == "Ole10Native") {
297      // Removing first 4 bytes which are the size
298         array = array.right(array.length() - 4);
299     }
300 
301     // Uncomment to write any ole file for testing
302     //POLE::Stream streamTemp(&storage, "Ole");
303     //QByteArray arrayTemp;
304     //arrayTemp.resize(streamTemp.size());
305     //streamTemp.read((unsigned char*)arrayTemp.data(), streamTemp.size());
306     //QFile file("olething.ole");
307     //file.open(QIODevice::WriteOnly);
308     //QDataStream out(&file);
309     //out.writeRawData(arrayTemp.data(), arrayTemp.length());
310 
311     debugMsooXml << "mode:" << outputStore->mode();
312     if (!outputStore->open(destinationName)) {
313         errorMessage = i18n("Could not open entry \"%1\" for writing.", destinationName);
314         return KoFilter::CreationError;
315     }
316 
317     QByteArray array2;
318     while (true) {
319         array2 = array.left(BLOCK_SIZE);
320         array = array.right(array.size() - array2.size());
321         const qint64 in = array2.size();
322         if (in <= 0) {
323             break;
324         }
325         char *block = array2.data();
326         if (in != outputStore->write(block, in)) {
327             errorMessage = i18n("Could not write block");
328             status = KoFilter::CreationError;
329             break;
330         }
331     }
332     outputStore->close();
333     delete inputDevice;
334     inputDevice = 0;
335     return status;
336 }
337 #undef BLOCK_SIZE
338 
339 #define BLOCK_SIZE 4096
createImage(QString & errorMessage,const QImage & source,KoStore * outputStore,const QString & destinationName)340 KoFilter::ConversionStatus Utils::createImage(QString& errorMessage,
341                                        const QImage& source, KoStore *outputStore,
342                                        const QString& destinationName)
343 {
344     if (outputStore->hasFile(destinationName)) {
345         return KoFilter::OK;
346     }
347 
348     KoFilter::ConversionStatus status = KoFilter::OK;
349     QByteArray array;
350     QBuffer inputDevice(&array);
351     inputDevice.open(QIODevice::ReadWrite);
352     QFileInfo info = QFileInfo(destinationName);
353     source.save(&inputDevice, info.suffix().toUtf8());
354     inputDevice.seek(0);
355 
356     if (!outputStore->open(destinationName)) {
357         errorMessage = i18n("Could not open entry \"%1\" for writing.", destinationName);
358         return KoFilter::CreationError;
359     }
360     char block[BLOCK_SIZE];
361     while (true) {
362         const qint64 in = inputDevice.read(block, BLOCK_SIZE);
363         if (in <= 0) {
364             break;
365         }
366         if (in != outputStore->write(block, in)) {
367             errorMessage = i18n("Could not write block");
368             status = KoFilter::CreationError;
369             break;
370         }
371     }
372     outputStore->close();
373     return status;
374 }
375 #undef BLOCK_SIZE
376 
377 #define BLOCK_SIZE 4096
copyFile(const KZip * zip,QString & errorMessage,const QString & sourceName,KoStore * outputStore,const QString & destinationName,bool oleType)378 KoFilter::ConversionStatus Utils::copyFile(const KZip* zip, QString& errorMessage,
379                                            const QString& sourceName, KoStore *outputStore,
380                                            const QString& destinationName, bool oleType)
381 {
382     if (outputStore->hasFile(destinationName)) {
383         return KoFilter::OK;
384     }
385 
386     KoFilter::ConversionStatus status;
387     if (oleType) {
388         status = copyOle(errorMessage, sourceName, outputStore, destinationName, zip);
389         return status;
390     }
391 
392     std::auto_ptr<QIODevice> inputDevice = std::auto_ptr<QIODevice>(Utils::openDeviceForFile(zip, errorMessage, sourceName, status));
393 
394     if (!inputDevice.get()) {
395         return status;
396     }
397 
398     debugMsooXml << "mode:" << outputStore->mode();
399     if (!outputStore->open(destinationName)) {
400         errorMessage = i18n("Could not open entry \"%1\" for writing.", destinationName);
401         return KoFilter::CreationError;
402     }
403     status = KoFilter::OK;
404     char block[BLOCK_SIZE];
405     while (true) {
406         const qint64 in = inputDevice->read(block, BLOCK_SIZE);
407 //        debugMsooXml << "in:" << in;
408         if (in <= 0)
409             break;
410         if (in != outputStore->write(block, in)) {
411             errorMessage = i18n("Could not write block");
412             status = KoFilter::CreationError;
413             break;
414         }
415     }
416     outputStore->close();
417     return status;
418 }
419 #undef BLOCK_SIZE
420 
imageSize(const KZip * zip,QString & errorMessage,const QString & sourceName,QSize * size)421 KoFilter::ConversionStatus Utils::imageSize(const KZip* zip, QString& errorMessage, const QString& sourceName,
422                                             QSize* size)
423 {
424     Q_ASSERT(size);
425     KoFilter::ConversionStatus status;
426     std::auto_ptr<QIODevice> inputDevice(Utils::openDeviceForFile(zip, errorMessage, sourceName, status));
427     if (!inputDevice.get()) {
428         return status;
429     }
430     QImageReader r(inputDevice.get(), QFileInfo(sourceName).suffix().toLatin1());
431     if (!r.canRead())
432         return KoFilter::WrongFormat;
433     *size = r.size();
434     debugMsooXml << *size;
435     return KoFilter::OK;
436 }
437 
loadThumbnail(QImage & thumbnail,KZip * zip)438 KoFilter::ConversionStatus Utils::loadThumbnail(QImage& thumbnail, KZip* zip)
439 {
440 //! @todo
441     Q_UNUSED(thumbnail)
442     Q_UNUSED(zip)
443     return KoFilter::FileNotFound;
444 }
445 
446 //! @return true if @a el has tag name is equal to @a expectedTag or false otherwise;
447 //!         on failure optional @a warningPrefix message is prepended to the warning
checkTag(const KoXmlElement & el,const char * expectedTag,const char * warningPrefix=0)448 static bool checkTag(const KoXmlElement& el, const char* expectedTag, const char* warningPrefix = 0)
449 {
450     if (el.tagName() != expectedTag) {
451         warnMsooXml
452         << (warningPrefix ? QString::fromLatin1(warningPrefix) + ":" : QString())
453         << "tag name=" << el.tagName() << " expected:" << expectedTag;
454         return false;
455     }
456     return true;
457 }
458 
459 //! @return true if @a el has namespace URI is equal to @a expectedNSURI or false otherwise
checkNsUri(const KoXmlElement & el,const char * expectedNsUri)460 static bool checkNsUri(const KoXmlElement& el, const char* expectedNsUri)
461 {
462     if (el.namespaceURI() != expectedNsUri) {
463         warnMsooXml << "Invalid namespace URI" << el.namespaceURI() << " expected:" << expectedNsUri;
464         return false;
465     }
466     return true;
467 }
468 
convertBooleanAttr(const QString & value,bool defaultValue)469 bool Utils::convertBooleanAttr(const QString& value, bool defaultValue)
470 {
471     const QByteArray val(value.toLatin1());
472     if (val.isEmpty()) {
473         return defaultValue;
474     }
475     debugMsooXml << val;
476 
477     return val != MsooXmlReader::constOff && val != MsooXmlReader::constFalse && val != MsooXmlReader::const0;
478 }
479 
loadContentTypes(const KoXmlDocument & contentTypesXML,QMultiHash<QByteArray,QByteArray> & contentTypes)480 KoFilter::ConversionStatus Utils::loadContentTypes(
481     const KoXmlDocument& contentTypesXML, QMultiHash<QByteArray, QByteArray>& contentTypes)
482 {
483     KoXmlElement typesEl(contentTypesXML.documentElement());
484     if (!checkTag(typesEl, "Types", "documentElement")) {
485         return KoFilter::WrongFormat;
486     }
487     if (!checkNsUri(typesEl, Schemas::contentTypes)) {
488         return KoFilter::WrongFormat;
489     }
490     KoXmlElement e;
491     forEachElement(e, typesEl) {
492         const QString tagName(e.tagName());
493         if (!checkNsUri(e, Schemas::contentTypes)) {
494             return KoFilter::WrongFormat;
495         }
496 
497         if (tagName == "Override") {
498             //ContentType -> PartName mapping
499             const QByteArray atrPartName(e.attribute("PartName").toLatin1());
500             const QByteArray atrContentType(e.attribute("ContentType").toLatin1());
501             if (atrPartName.isEmpty() || atrContentType.isEmpty()) {
502                 warnMsooXml << "Invalid data for" << tagName
503                 << "element: PartName=" << atrPartName << "ContentType=" << atrContentType;
504                 return KoFilter::WrongFormat;
505             }
506 //debugMsooXml << atrContentType << "->" << atrPartName;
507             contentTypes.insert(atrContentType, atrPartName);
508         } else if (tagName == "Default") {
509 //! @todo
510             // skip for now...
511         }
512     }
513     return KoFilter::OK;
514 }
515 
loadDocumentProperties(const KoXmlDocument & appXML,QMap<QString,QVariant> & properties)516 KoFilter::ConversionStatus Utils::loadDocumentProperties(const KoXmlDocument& appXML, QMap<QString, QVariant>& properties)
517 {
518     KoXmlElement typesEl(appXML.documentElement());
519     KoXmlElement e, elem, element;
520     forEachElement(element, typesEl) {
521         QVariant v;
522         forEachElement(elem, element) {
523             if(elem.tagName() == "vector") {
524                 QVariantList list;
525                 forEachElement(e, elem)
526                     list.append(e.text());
527                 v = list;
528             }
529         }
530         if(!v.isValid())
531             v = element.text();
532         properties[element.tagName()] = v;
533     }
534     return KoFilter::OK;
535 }
536 
ST_Lang_to_languageAndCountry(const QString & value,QString & language,QString & country)537 bool Utils::ST_Lang_to_languageAndCountry(const QString& value, QString& language, QString& country)
538 {
539     int indexForCountry =  value.indexOf('-');
540     if (indexForCountry <= 0)
541         return false;
542     indexForCountry++;
543     language = value.left(indexForCountry - 1);
544     country = value.mid(indexForCountry);
545     return !country.isEmpty();
546 }
547 
548 class ST_HighlightColorMapping : public QHash<QString, QColor>
549 {
550 public:
ST_HighlightColorMapping()551     ST_HighlightColorMapping() {
552 #define INSERT_HC(c, hex) insert(QLatin1String(c), QColor( QRgb( 0xff000000 | hex ) ) )
553         INSERT_HC("black", 0x000000);
554         INSERT_HC("blue", 0x0000ff);
555         INSERT_HC("cyan", 0x00ffff);
556         INSERT_HC("darkBlue", 0x000080);
557         INSERT_HC("darkCyan", 0x008080);
558         INSERT_HC("darkGray", 0x808080);
559         INSERT_HC("darkGreen", 0x008000);
560         INSERT_HC("darkMagenta", 0x800080);
561         INSERT_HC("darkRed", 0x800000);
562         INSERT_HC("darkYellow", 0x808000);
563         INSERT_HC("green", 0x00ff00);
564         INSERT_HC("lightGray", 0xc0c0c0);
565         INSERT_HC("magenta", 0xff00ff);
566         INSERT_HC("red", 0xff0000);
567         INSERT_HC("yellow", 0xffff00);
568         INSERT_HC("white", 0xffffff);
569 #undef INSERT_HC
570     }
571 };
572 
Q_GLOBAL_STATIC(ST_HighlightColorMapping,s_ST_HighlightColor_to_QColor)573 Q_GLOBAL_STATIC(ST_HighlightColorMapping, s_ST_HighlightColor_to_QColor)
574 
575 QBrush Utils::ST_HighlightColor_to_QColor(const QString& colorName)
576 {
577     const QColor c(s_ST_HighlightColor_to_QColor->value(colorName));
578     if (c.isValid())
579         return QBrush(c);
580     return QBrush(); // for "none" or anything unsupported
581 }
582 
ST_Percentage_to_double(const QString & val,bool & ok)583 qreal Utils::ST_Percentage_to_double(const QString& val, bool& ok)
584 {
585     if (!val.endsWith('%')) {
586         ok = false;
587         return 0.0;
588     }
589     QString result(val);
590     result.truncate(1);
591     return result.toDouble(&ok);
592 }
593 
ST_Percentage_withMsooxmlFix_to_double(const QString & val,bool & ok)594 qreal Utils::ST_Percentage_withMsooxmlFix_to_double(const QString& val, bool& ok)
595 {
596     const qreal result = ST_Percentage_to_double(val, ok);
597     if (ok)
598         return result;
599     // MSOOXML fix: the format is int({ST_Percentage}*1000)
600     const int resultInt = val.toInt(&ok);
601     if (!ok)
602         return 0.0;
603     return qreal(resultInt) / 1000.0;
604 }
605 
colorForLuminance(const QColor & color,const DoubleModifier & modulation,const DoubleModifier & offset)606 QColor Utils::colorForLuminance(const QColor& color, const DoubleModifier& modulation, const DoubleModifier& offset)
607 {
608     if (modulation.valid) {
609         int r, g, b;
610         color.getRgb(&r, &g, &b);
611         if (offset.valid) {
612             return QColor(
613                        int(floor((255 - r) * (100.0 - modulation.value) / 100.0 + r)),
614                        int(floor((255 - g) * offset.value / 100.0 + g)),
615                        int(floor((255 - b) * offset.value / 100.0 + b)),
616                        color.alpha());
617         } else {
618             return QColor(
619                        int(floor(r * modulation.value / 100.0)),
620                        int(floor(g * modulation.value / 100.0)),
621                        int(floor(b * modulation.value / 100.0)),
622                        color.alpha());
623         }
624     }
625     return color;
626 }
627 
modifyColor(QColor & color,qreal tint,qreal shade,qreal satMod)628 KOMSOOXML_EXPORT void Utils::modifyColor(QColor& color, qreal tint, qreal shade, qreal satMod)
629 {
630     int red = color.red();
631     int green = color.green();
632     int blue = color.blue();
633 
634     if (tint > 0) {
635         red = tint * red + (1 - tint) * 255;
636         green = tint * green + (1 - tint) * 255;
637         blue = tint * blue + (1 - tint) * 255;
638     }
639     if (shade > 0) {
640         red = shade * red;
641         green = shade * green;
642         blue = shade * blue;
643     }
644 
645     // FIXME: This calculation for sure is incorrect,
646     // According to MS forums, RGB should first be converted to linear RGB
647     // Then to HSL and then multiply saturation value by satMod
648     // SatMod can be for example 3.5 so converting RGB -> HSL is not an option
649     // ADD INFO: MS document does not say that when calculating TINT and SHADE
650     // That whether one should use normal RGB or linear RGB, check it!
651 
652 
653     // This method is used temporarily, it seems to produce visually better results than the lower one.
654     if (satMod > 0) {
655         QColor temp = QColor(red, green, blue);
656         qreal saturationFromFull = 1.0 - temp.saturationF();
657         temp = QColor::fromHsvF(temp.hueF(), temp.saturationF() + saturationFromFull / 10 * satMod, temp.valueF());
658         red = temp.red();
659         green = temp.green();
660         blue = temp.blue();
661     }
662 
663     /*
664     if (satMod > 0) {
665         red = red * satMod;
666         green = green * satMod;
667         blue = blue * satMod;
668         if (red > 255) {
669             red = 255;
670         }
671         if (green > 255) {
672             green = 255;
673         }
674         if (blue > 255) {
675             blue = 255;
676         }
677     }
678     */
679 
680     color = QColor(red, green, blue);
681 }
682 
683 class ST_PlaceholderType_to_ODFMapping : public QHash<QByteArray, QByteArray>
684 {
685 public:
ST_PlaceholderType_to_ODFMapping()686     ST_PlaceholderType_to_ODFMapping() {
687         insert("body", "outline");
688         insert("chart", "chart");
689         insert("clipArt", "graphic");
690         insert("ctrTitle", "title");
691 //! @todo dgm->orgchart?
692         insert("dgm", "orgchart");
693         insert("dt", "date-time");
694         insert("ftr", "footer");
695         insert("hdr", "header");
696 //! @todo media->object?
697         insert("media", "object");
698         insert("obj", "object");
699         insert("pic", "graphic");
700 //! @todo sldImg->graphic?
701         insert("sldImg", "graphic");
702         insert("sldNum", "page-number");
703         insert("subTitle", "subtitle");
704         insert("tbl", "table");
705         insert("title", "title");
706     }
707 };
708 
Q_GLOBAL_STATIC(ST_PlaceholderType_to_ODFMapping,s_ST_PlaceholderType_to_ODF)709 Q_GLOBAL_STATIC(ST_PlaceholderType_to_ODFMapping, s_ST_PlaceholderType_to_ODF)
710 
711 QString Utils::ST_PlaceholderType_to_ODF(const QString& ecmaType)
712 {
713     QHash<QByteArray, QByteArray>::ConstIterator it(s_ST_PlaceholderType_to_ODF->constFind(ecmaType.toLatin1()));
714     if (it == s_ST_PlaceholderType_to_ODF->constEnd())
715         return QLatin1String("text");
716     return QString(it.value());
717 }
718 
719 //! Mapping for handling u element, used in setupUnderLineStyle()
720 struct UnderlineStyle {
UnderlineStyleUnderlineStyle721     UnderlineStyle(
722         KoCharacterStyle::LineStyle style_,
723         KoCharacterStyle::LineType type_,
724         KoCharacterStyle::LineWeight weight_,
725         KoCharacterStyle::LineMode mode_ = KoCharacterStyle::ContinuousLineMode)
726             : style(style_), type(type_), weight(weight_), mode(mode_) {
727     }
728 
729     KoCharacterStyle::LineStyle style;
730     KoCharacterStyle::LineType type;
731     KoCharacterStyle::LineWeight weight;
732     KoCharacterStyle::LineMode mode;
733 };
734 
735 typedef QHash<QByteArray, UnderlineStyle*> UnderlineStylesHashBase;
736 
737 class UnderlineStylesHash : public UnderlineStylesHashBase
738 {
739 public:
UnderlineStylesHash()740     UnderlineStylesHash() {
741         // default:
742         insert("-",
743                new UnderlineStyle(KoCharacterStyle::SolidLine, KoCharacterStyle::SingleLine,
744                                   KoCharacterStyle::AutoLineWeight)
745               );
746         // 17.18.99 ST_Underline (Underline Patterns), WML ECMA-376 p.1681:
747         insert("single",
748                new UnderlineStyle(KoCharacterStyle::SolidLine, KoCharacterStyle::SingleLine,
749                                   KoCharacterStyle::AutoLineWeight)
750               );
751         insert("double",
752                new UnderlineStyle(KoCharacterStyle::SolidLine, KoCharacterStyle::DoubleLine,
753                                   KoCharacterStyle::AutoLineWeight)
754               );
755         insert("dbl",
756                new UnderlineStyle(KoCharacterStyle::SolidLine, KoCharacterStyle::DoubleLine,
757                                   KoCharacterStyle::AutoLineWeight)
758               );
759         insert("words",
760                new UnderlineStyle(KoCharacterStyle::SolidLine, KoCharacterStyle::SingleLine,
761                                   KoCharacterStyle::AutoLineWeight, KoCharacterStyle::SkipWhiteSpaceLineMode)
762               );
763         insert("thick",
764                new UnderlineStyle(KoCharacterStyle::SolidLine, KoCharacterStyle::SingleLine,
765                                   KoCharacterStyle::BoldLineWeight)
766               );
767         insert("dash",
768                new UnderlineStyle(KoCharacterStyle::DashLine, KoCharacterStyle::SingleLine,
769                                   KoCharacterStyle::AutoLineWeight)
770               );
771         insert("dashDotHeavy",
772                new UnderlineStyle(KoCharacterStyle::DotDashLine, KoCharacterStyle::SingleLine,
773                                   KoCharacterStyle::BoldLineWeight)
774               );
775         insert("dotted",
776                new UnderlineStyle(KoCharacterStyle::DottedLine, KoCharacterStyle::SingleLine,
777                                   KoCharacterStyle::AutoLineWeight)
778               );
779         insert("dotDash",
780                new UnderlineStyle(KoCharacterStyle::DotDashLine, KoCharacterStyle::SingleLine,
781                                   KoCharacterStyle::AutoLineWeight)
782               );
783         insert("dotDotDash",
784                new UnderlineStyle(KoCharacterStyle::DotDotDashLine, KoCharacterStyle::SingleLine,
785                                   KoCharacterStyle::AutoLineWeight)
786               );
787         insert("wave",
788                new UnderlineStyle(KoCharacterStyle::WaveLine, KoCharacterStyle::SingleLine,
789                                   KoCharacterStyle::AutoLineWeight)
790               );
791         insert("wavyDouble",
792                new UnderlineStyle(KoCharacterStyle::WaveLine, KoCharacterStyle::DoubleLine,
793                                   KoCharacterStyle::AutoLineWeight)
794               );
795         insert("wavyDbl",
796                new UnderlineStyle(KoCharacterStyle::WaveLine, KoCharacterStyle::DoubleLine,
797                                   KoCharacterStyle::AutoLineWeight)
798               );
799         insert("wavyHeavy",
800                new UnderlineStyle(KoCharacterStyle::WaveLine, KoCharacterStyle::SingleLine,
801                                   KoCharacterStyle::BoldLineWeight)
802               );
803 //! @todo more styles
804 
805         // 20.1.10.82 ST_TextUnderlineType (Text Underline Types), DrawingML ECMA-376 p.3450:
806         insert("none",
807                new UnderlineStyle(KoCharacterStyle::NoLineStyle, KoCharacterStyle::NoLineType,
808                                   KoCharacterStyle::AutoLineWeight)
809               );
810         insert("sng",
811                new UnderlineStyle(KoCharacterStyle::SolidLine, KoCharacterStyle::SingleLine,
812                                   KoCharacterStyle::AutoLineWeight)
813               );
814 //! @todo more styles
815     }
816 
~UnderlineStylesHash()817     ~UnderlineStylesHash() {
818         qDeleteAll(*this);
819     }
820 
setup(const QString & msooxmlName,KoCharacterStyle * textStyleProperties)821     void setup(const QString& msooxmlName,
822                KoCharacterStyle* textStyleProperties) {
823         UnderlineStyle* style = value(msooxmlName.toLatin1());
824         if (!style)
825             style = value("-");
826         textStyleProperties->setUnderlineStyle(style->style);
827         // add style:text-underline-type if it is not "single"
828         if (KoCharacterStyle::SingleLine != style->type) {
829             textStyleProperties->setUnderlineType(style->type);
830         }
831         textStyleProperties->setUnderlineWidth(style->weight, 1.0);
832         // add style:text-underline-mode if it is not "continuous"
833         if (KoCharacterStyle::ContinuousLineMode != style->mode) {
834             textStyleProperties->setUnderlineMode(style->mode);
835         }
836     }
837 };
838 
rotateString(const qreal rotation,const qreal width,const qreal height,qreal & angle,qreal & xDiff,qreal & yDiff)839 void Utils::rotateString(const qreal rotation, const qreal width, const qreal height, qreal& angle, qreal& xDiff, qreal& yDiff)
840 {
841     angle = -(qreal)rotation * ((qreal)(M_PI) / (qreal)180.0)/ (qreal)60000.0;
842     //position change is calculated based on the fact that center point stays in the same location
843     // Width/2 = Xnew + cos(angle)*Width/2 - sin(angle)*Height/2
844     // Height/2 = Ynew + sin(angle)*Width/2 + cos(angle)*Height/2
845     xDiff = width/2 - cos(-angle)*width/2 + sin(-angle)*height/2;
846     yDiff = height/2 - sin(-angle)*width/2 - cos(-angle)*height/2;
847 }
848 
849 
Q_GLOBAL_STATIC(UnderlineStylesHash,s_underLineStyles)850 Q_GLOBAL_STATIC(UnderlineStylesHash, s_underLineStyles)
851 
852 void Utils::setupUnderLineStyle(const QString& msooxmlName, KoCharacterStyle* textStyleProperties)
853 {
854     s_underLineStyles->setup(msooxmlName, textStyleProperties);
855 }
856 
857 //-----------------------------------------
858 // Marker styles
859 //-----------------------------------------
860 
861 namespace
862 {
863     static const char* const markerStyles[6] = {
864         "", "msArrowEnd_20_5", "msArrowStealthEnd_20_5", "msArrowDiamondEnd_20_5",
865         "msArrowOvalEnd_20_5", "msArrowOpenEnd_20_5"
866     };
867 
868     // trying to maintain compatibility with libmso
869     enum MSOLINEEND_CUSTOM {
870         msolineNoEnd,
871         msolineArrowEnd,
872         msolineArrowStealthEnd,
873         msolineArrowDiamondEnd,
874         msolineArrowOvalEnd,
875         msolineArrowOpenEnd
876     };
877 }
878 
defineMarkerStyle(KoGenStyles & mainStyles,const QString & type)879 QString Utils::defineMarkerStyle(KoGenStyles& mainStyles, const QString& type)
880 {
881     uint id;
882 
883     if (type == "arrow") {
884         id = msolineArrowOpenEnd;
885     } else if (type == "stealth") {
886         id = msolineArrowStealthEnd;
887     } else if (type == "diamond") {
888         id = msolineArrowDiamondEnd;
889     } else if (type == "oval") {
890         id = msolineArrowOvalEnd;
891     } else if (type == "triangle") {
892         id = msolineArrowEnd;
893     } else {
894         return QString();
895     }
896 
897     const QString name(markerStyles[id]);
898 
899     if (mainStyles.style(name, "")) {
900         return name;
901     }
902 
903     KoGenStyle marker(KoGenStyle::MarkerStyle);
904     marker.addAttribute("draw:display-name",  QString(markerStyles[id]).replace("_20_", " "));
905 
906     // sync with LO
907     switch (id) {
908     case msolineArrowStealthEnd:
909         marker.addAttribute("svg:viewBox", "0 0 318 318");
910         marker.addAttribute("svg:d", "m159 0 159 318-159-127-159 127z");
911         break;
912     case msolineArrowDiamondEnd:
913         marker.addAttribute("svg:viewBox", "0 0 318 318");
914         marker.addAttribute("svg:d", "m159 0 159 159-159 159-159-159z");
915         break;
916     case msolineArrowOvalEnd:
917         marker.addAttribute("svg:viewBox", "0 0 318 318");
918         marker.addAttribute("svg:d", "m318 0c0-87-72-159-159-159s-159 72-159 159 72 159 159 159 159-72 159-159z");
919         break;
920     case msolineArrowOpenEnd:
921         marker.addAttribute("svg:viewBox", "0 0 477 477");
922         marker.addAttribute("svg:d", "m239 0 238 434-72 43-166-305-167 305-72-43z");
923         break;
924     case msolineArrowEnd:
925     default:
926         marker.addAttribute("svg:viewBox", "0 0 318 318");
927         marker.addAttribute("svg:d", "m159 0 159 318h-318z");
928         break;
929     }
930     return mainStyles.insert(marker, name, KoGenStyles::DontAddNumberToName);
931 }
932 
defineMarkerWidth(const QString & markerWidth,const qreal lineWidth)933 qreal Utils::defineMarkerWidth(const QString &markerWidth, const qreal lineWidth)
934 {
935     int c = 0;
936 
937     if (markerWidth == "lg") {
938         c = 3;
939     } else if (markerWidth == "med" || markerWidth.isEmpty()) {
940         c = 2; //MSOOXML default = "med"
941     } else if (markerWidth == "sm") {
942         c = 1;
943     }
944     return ( lineWidth * c );
945 }
946 
947 //-----------------------------------------
948 // XmlWriteBuffer
949 //-----------------------------------------
950 
XmlWriteBuffer()951 Utils::XmlWriteBuffer::XmlWriteBuffer()
952         : m_origWriter(0), m_newWriter(0)
953 {
954 }
955 
~XmlWriteBuffer()956 Utils::XmlWriteBuffer::~XmlWriteBuffer()
957 {
958     releaseWriterInternal();
959 }
960 
setWriter(KoXmlWriter * writer)961 KoXmlWriter* Utils::XmlWriteBuffer::setWriter(KoXmlWriter* writer)
962 {
963     Q_ASSERT(!m_origWriter && !m_newWriter);
964     if (m_origWriter || m_newWriter) {
965         return 0;
966     }
967     m_origWriter = writer; // remember
968     m_newWriter = new KoXmlWriter(&m_buffer, m_origWriter->indentLevel() + 1);
969     return m_newWriter;
970 }
971 
releaseWriter()972 KoXmlWriter* Utils::XmlWriteBuffer::releaseWriter()
973 {
974     Q_ASSERT(m_newWriter && m_origWriter);
975     if (!m_newWriter || !m_origWriter) {
976         return 0;
977     }
978     m_origWriter->addCompleteElement(&m_buffer);
979     return releaseWriterInternal();
980 }
981 
releaseWriter(QString & bkpXmlSnippet)982 KoXmlWriter* Utils::XmlWriteBuffer::releaseWriter(QString& bkpXmlSnippet)
983 {
984     Q_ASSERT(m_newWriter && m_origWriter);
985     if (!m_newWriter || !m_origWriter) {
986         return 0;
987     }
988     bkpXmlSnippet = QString::fromUtf8(m_buffer.buffer(), m_buffer.buffer().size());
989     return releaseWriterInternal();
990 }
991 
releaseWriterInternal()992 KoXmlWriter* Utils::XmlWriteBuffer::releaseWriterInternal()
993 {
994     if (!m_newWriter || !m_origWriter) {
995         return 0;
996     }
997     delete m_newWriter;
998     m_newWriter = 0;
999     KoXmlWriter* tmp = m_origWriter;
1000     m_origWriter = 0;
1001     return tmp;
1002 }
1003 
clear()1004 void Utils::XmlWriteBuffer::clear()
1005 {
1006     delete m_newWriter;
1007     m_newWriter = 0;
1008     m_origWriter = 0;
1009 }
1010 
columnName(uint column)1011 QString Utils::columnName(uint column)
1012 {
1013     uint digits = 1;
1014     uint offset = 0;
1015 
1016     for (uint limit = 26; column >= limit + offset; limit *= 26, digits++)
1017         offset += limit;
1018 
1019     QString str;
1020     for (uint col = column - offset; digits > 0; --digits, col /= 26)
1021         str.prepend(QChar('A' + (col % 26)));
1022 
1023     return str;
1024 }
1025 
splitPathAndFile(const QString & pathAndFile,QString * path,QString * file)1026 void Utils::splitPathAndFile(const QString& pathAndFile, QString* path, QString* file)
1027 {
1028     Q_ASSERT(path);
1029     Q_ASSERT(file);
1030     *path = pathAndFile.left(pathAndFile.lastIndexOf('/'));
1031     *file = pathAndFile.mid(pathAndFile.lastIndexOf('/') + 1);
1032 }
1033 
1034 // <units> -------------------
1035 
EMU_to_ODF(const QString & twipValue)1036 QString Utils::EMU_to_ODF(const QString& twipValue)
1037 {
1038     if (twipValue.isEmpty())
1039         return QLatin1String("0cm");
1040     bool ok;
1041     const int emu = twipValue.toInt(&ok);
1042     if (!ok)
1043         return QString();
1044     if (emu == 0)
1045         return QLatin1String("0cm");
1046     return EMU_TO_CM_STRING(emu);
1047 }
1048 
TWIP_to_ODF(const QString & twipValue)1049 QString Utils::TWIP_to_ODF(const QString& twipValue)
1050 {
1051     if (twipValue.isEmpty())
1052         return QLatin1String("0cm");
1053     bool ok;
1054     const int twip = twipValue.toInt(&ok);
1055     if (!ok)
1056         return QString();
1057     if (twip == 0)
1058         return QLatin1String("0cm");
1059     return cmString(TWIP_TO_CM(qreal(twip)));
1060 }
1061 
ST_EighthPointMeasure_to_ODF(const QString & value)1062 QString Utils::ST_EighthPointMeasure_to_ODF(const QString& value)
1063 {
1064     if (value.isEmpty())
1065         return QString();
1066     bool ok;
1067     const qreal point = qreal(value.toFloat(&ok)) / 8.0;
1068     if (!ok)
1069         return QString();
1070     return QString::number(point, 'g', 2) + QLatin1String("pt");
1071 }
1072 
1073 //! @return true if @a string is non-negative integer number
isPositiveIntegerNumber(const QString & string)1074 static bool isPositiveIntegerNumber(const QString& string)
1075 {
1076     for (const QChar *c = string.constData(); !c->isNull(); c++) {
1077         if (!c->isNumber())
1078             return false;
1079     }
1080     return !string.isEmpty();
1081 }
1082 
1083 //! Splits number and unit
splitNumberAndUnit(const QString & _string,qreal * number,QString * unit)1084 static bool splitNumberAndUnit(const QString& _string, qreal *number, QString* unit)
1085 {
1086     int unitIndex = 0;
1087     QString string(_string);
1088     for (const QChar *c = string.constData(); !c->isNull(); c++, unitIndex++) {
1089         if (!c->isNumber() && *c != '.')
1090             break;
1091     }
1092     *unit = string.mid(unitIndex);
1093     string.truncate(unitIndex);
1094     if (string.isEmpty()) {
1095         warnMsooXml << "No unit found in" << _string;
1096         return false;
1097     }
1098     bool ok;
1099     *number = string.toFloat(&ok);
1100     if (!ok)
1101         warnMsooXml << "Invalid number in" << _string;
1102     return ok;
1103 }
1104 
1105 //! @return true is @a unit is one of these mentioned in 22.9.2.15 ST_UniversalMeasure (Universal Measurement)
isUnitAcceptable(const QString & unit)1106 static bool isUnitAcceptable(const QString& unit)
1107 {
1108     if (unit.length() != 2)
1109         return false;
1110     return unit == QString::fromLatin1("cm")
1111            || unit == QString::fromLatin1("mm")
1112            || unit == QString::fromLatin1("in")
1113            || unit == QString::fromLatin1("pt")
1114            || unit == QString::fromLatin1("pc")
1115            || unit == QString::fromLatin1("pi");
1116 }
1117 
ST_TwipsMeasure_to_ODF_with_unit(const QString & value,qreal (* convertFromTwips)(qreal),const char * unit)1118 static QString ST_TwipsMeasure_to_ODF_with_unit(const QString& value,
1119                                                 qreal (*convertFromTwips)(qreal), const char* unit)
1120 {
1121     if (value.isEmpty())
1122         return QString();
1123     if (isPositiveIntegerNumber(value)) {
1124         // a positive number in twips (twentieths of a point, equivalent to 1/1440th of an inch)
1125         bool ok;
1126         const qreal point = convertFromTwips( qreal(value.toFloat(&ok)) );
1127         if (!ok)
1128             return QString();
1129         return QString::number(point, 'g', 2) + QLatin1String(unit);
1130     }
1131     return Utils::ST_PositiveUniversalMeasure_to_ODF(value);
1132 }
1133 
twipToPt(qreal v)1134 qreal twipToPt(qreal v)
1135 {
1136     return TWIP_TO_POINT(v);
1137 }
1138 
ST_TwipsMeasure_to_pt(const QString & value)1139 KOMSOOXML_EXPORT QString Utils::ST_TwipsMeasure_to_pt(const QString& value)
1140 {
1141     return ST_TwipsMeasure_to_ODF_with_unit(value, twipToPt, "pt");
1142 }
1143 
twipToCm(qreal v)1144 qreal twipToCm(qreal v)
1145 {
1146     return TWIP_TO_CM(v);
1147 }
1148 
ST_TwipsMeasure_to_cm(const QString & value)1149 KOMSOOXML_EXPORT QString Utils::ST_TwipsMeasure_to_cm(const QString& value)
1150 {
1151     return ST_TwipsMeasure_to_ODF_with_unit(value, twipToCm, "cm");
1152 }
1153 
ST_PositiveUniversalMeasure_to_ODF(const QString & value)1154 KOMSOOXML_EXPORT QString Utils::ST_PositiveUniversalMeasure_to_ODF(const QString& value)
1155 {
1156     // a positive decimal number immediately following by a unit identifier.
1157     qreal number(0.0);
1158     QString unit;
1159     if (!splitNumberAndUnit(value, &number, &unit))
1160         return QString();
1161     // special case: pc is another name for pica
1162     if (unit == QString::fromLatin1("pc")) {
1163         return QString::number(number) + QLatin1String("pi");
1164     }
1165     if (!isUnitAcceptable(unit)) {
1166         warnMsooXml << "Unit" << unit << "not supported. Expected cm/mm/in/pt/pc/pi.";
1167         return QString();
1168     }
1169     return value; // the original is OK
1170 }
1171 
ST_PositiveUniversalMeasure_to_cm(const QString & value)1172 KOMSOOXML_EXPORT QString Utils::ST_PositiveUniversalMeasure_to_cm(const QString& value)
1173 {
1174     QString v(ST_PositiveUniversalMeasure_to_ODF(value));
1175     if (v.isEmpty())
1176         return QString();
1177     return cmString(POINT_TO_CM(KoUnit::parseValue(v)));
1178 }
1179 
1180 // </units> -------------------
1181 
ParagraphBulletProperties()1182 Utils::ParagraphBulletProperties::ParagraphBulletProperties()
1183 {
1184     clear();
1185 }
1186 
clear()1187 void Utils::ParagraphBulletProperties::clear()
1188 {
1189     m_level = -1;
1190     m_type = ParagraphBulletProperties::DefaultType;
1191     m_startValue = "1"; //ECMA-376, p.4575
1192     m_bulletFont = UNUSED;
1193     m_bulletChar = UNUSED;
1194     m_numFormat = UNUSED;
1195     m_prefix = UNUSED;
1196     m_suffix = UNUSED;
1197     m_align = UNUSED;
1198     m_indent = UNUSED;
1199     m_margin = UNUSED;
1200     m_picturePath = UNUSED;
1201     m_bulletColor = UNUSED;
1202     m_followingChar = UNUSED;
1203     m_bulletRelativeSize = UNUSED;
1204     m_bulletSize = UNUSED;
1205     m_startOverride = false;
1206 }
1207 
isEmpty() const1208 bool Utils::ParagraphBulletProperties::isEmpty() const
1209 {
1210     if (m_type == ParagraphBulletProperties::DefaultType) {
1211         return true;
1212     }
1213     return false;
1214 }
1215 
setAlign(const QString & align)1216 void Utils::ParagraphBulletProperties::setAlign(const QString& align)
1217 {
1218     m_align = align;
1219 }
1220 
setBulletChar(const QString & bulletChar)1221 void Utils::ParagraphBulletProperties::setBulletChar(const QString& bulletChar)
1222 {
1223     m_bulletChar = bulletChar;
1224     m_type = ParagraphBulletProperties::BulletType;
1225 }
1226 
setStartValue(const QString & value)1227 void Utils::ParagraphBulletProperties::setStartValue(const QString& value)
1228 {
1229     m_startValue = value;
1230 }
1231 
setMargin(const qreal margin)1232 void Utils::ParagraphBulletProperties::setMargin(const qreal margin)
1233 {
1234     m_margin = QString("%1").arg(margin);
1235 }
1236 
setIndent(const qreal indent)1237 void Utils::ParagraphBulletProperties::setIndent(const qreal indent)
1238 {
1239     m_indent = QString("%1").arg(indent);
1240 }
1241 
setPrefix(const QString & prefixChar)1242 void Utils::ParagraphBulletProperties::setPrefix(const QString& prefixChar)
1243 {
1244     m_prefix = prefixChar;
1245 }
1246 
setSuffix(const QString & suffixChar)1247 void Utils::ParagraphBulletProperties::setSuffix(const QString& suffixChar)
1248 {
1249     m_suffix = suffixChar;
1250 }
1251 
setNumFormat(const QString & numFormat)1252 void Utils::ParagraphBulletProperties::setNumFormat(const QString& numFormat)
1253 {
1254     m_numFormat = numFormat;
1255     m_type = ParagraphBulletProperties::NumberType;
1256 }
1257 
setPicturePath(const QString & picturePath)1258 void Utils::ParagraphBulletProperties::setPicturePath(const QString& picturePath)
1259 {
1260     m_picturePath = picturePath;
1261     m_type = ParagraphBulletProperties::PictureType;
1262 }
1263 
setBulletRelativeSize(const int size)1264 void Utils::ParagraphBulletProperties::setBulletRelativeSize(const int size)
1265 {
1266     m_bulletRelativeSize = QString("%1").arg(size);
1267 }
1268 
setBulletSizePt(const qreal size)1269 void Utils::ParagraphBulletProperties::setBulletSizePt(const qreal size)
1270 {
1271     m_bulletSize = QString("%1").arg(size);
1272 }
1273 
setBulletFont(const QString & font)1274 void Utils::ParagraphBulletProperties::setBulletFont(const QString& font)
1275 {
1276     m_bulletFont = font;
1277 }
1278 
setBulletColor(const QString & bulletColor)1279 void Utils::ParagraphBulletProperties::setBulletColor(const QString& bulletColor)
1280 {
1281     m_bulletColor = bulletColor;
1282 }
1283 
setFollowingChar(const QString & followingChar)1284 void Utils::ParagraphBulletProperties::setFollowingChar(const QString& followingChar)
1285 {
1286     m_followingChar = followingChar;
1287 }
1288 
setTextStyle(const KoGenStyle & textStyle)1289 void Utils::ParagraphBulletProperties::setTextStyle(const KoGenStyle& textStyle)
1290 {
1291     m_textStyle = textStyle;
1292 
1293     //m_bulletFont
1294     if (!(m_textStyle.property("fo:font-family")).isEmpty()) {
1295         m_bulletFont = m_textStyle.property("fo:font-family");
1296     }
1297     if (!(m_textStyle.property("style:font-name")).isEmpty()) {
1298         m_bulletFont = m_textStyle.property("style:font-name");
1299     }
1300     //m_bulletColor
1301     if (!(m_textStyle.property("fo:color")).isEmpty()) {
1302         m_bulletColor = m_textStyle.property("fo:color");
1303     }
1304     //m_bulletRelativeSize
1305     //m_bulletSize
1306     if (!m_textStyle.property("fo:font-size").isEmpty()) {
1307         QString bulletSize = m_textStyle.property("fo:font-size");
1308         if (bulletSize.endsWith(QLatin1Char('%'))) {
1309             bulletSize.chop(1);
1310             m_bulletRelativeSize = bulletSize;
1311         } else if (bulletSize.endsWith(QLatin1String("pt"))) {
1312             bulletSize.chop(2);
1313             m_bulletSize = bulletSize;
1314         } else {
1315             debugMsooXml << "Unit of font-size NOT supported!";
1316         }
1317     }
1318 }
1319 
setStartOverride(const bool startOverride)1320 void Utils::ParagraphBulletProperties::setStartOverride(const bool startOverride)
1321 {
1322     m_startOverride = startOverride;
1323 }
1324 
startValue() const1325 QString Utils::ParagraphBulletProperties::startValue() const
1326 {
1327     return m_startValue;
1328 }
1329 
bulletColor() const1330 QString Utils::ParagraphBulletProperties::bulletColor() const
1331 {
1332     return m_bulletColor;
1333 }
1334 
bulletChar() const1335 QString Utils::ParagraphBulletProperties::bulletChar() const
1336 {
1337     return m_bulletChar;
1338 }
1339 
bulletFont() const1340 QString Utils::ParagraphBulletProperties::bulletFont() const
1341 {
1342     return m_bulletFont;
1343 }
1344 
margin() const1345 QString Utils::ParagraphBulletProperties::margin() const
1346 {
1347     return m_margin;
1348 }
1349 
indent() const1350 QString Utils::ParagraphBulletProperties::indent() const
1351 {
1352     return m_indent;
1353 }
1354 
bulletRelativeSize() const1355 QString Utils::ParagraphBulletProperties::bulletRelativeSize() const
1356 {
1357     return m_bulletRelativeSize;
1358 }
1359 
bulletSizePt() const1360 QString Utils::ParagraphBulletProperties::bulletSizePt() const
1361 {
1362     return m_bulletSize;
1363 }
1364 
followingChar() const1365 QString Utils::ParagraphBulletProperties::followingChar() const
1366 {
1367     return m_followingChar;
1368 }
1369 
textStyle() const1370 KoGenStyle Utils::ParagraphBulletProperties::textStyle() const
1371 {
1372     return m_textStyle;
1373 }
1374 
startOverride() const1375 bool Utils::ParagraphBulletProperties::startOverride() const
1376 {
1377     return m_startOverride;
1378 }
1379 
addInheritedValues(const ParagraphBulletProperties & properties)1380 void Utils::ParagraphBulletProperties::addInheritedValues(const ParagraphBulletProperties& properties)
1381 {
1382     // This function is intented for helping to inherit some values from other properties
1383     if (m_level == -1) {
1384         m_level = properties.m_level;
1385     }
1386     if (properties.m_type != ParagraphBulletProperties::DefaultType) {
1387         m_type = properties.m_type;
1388     }
1389     if (properties.m_startValue != "1") {
1390         m_startValue = properties.m_startValue;
1391     }
1392     if (properties.m_bulletFont != UNUSED) {
1393         m_bulletFont = properties.m_bulletFont;
1394     }
1395     if (properties.m_bulletChar != UNUSED) {
1396         m_bulletChar = properties.m_bulletChar;
1397     }
1398     if (properties.m_numFormat != UNUSED) {
1399         m_numFormat = properties.m_numFormat;
1400     }
1401     if (properties.m_prefix != UNUSED) {
1402         m_prefix = properties.m_prefix;
1403     }
1404     if (properties.m_suffix != UNUSED) {
1405         m_suffix = properties.m_suffix;
1406     }
1407     if (properties.m_align != UNUSED) {
1408         m_align = properties.m_align;
1409     }
1410     if (properties.m_indent != UNUSED) {
1411         m_indent = properties.m_indent;
1412     }
1413     if (properties.m_margin != UNUSED) {
1414         m_margin = properties.m_margin;
1415     }
1416     if (properties.m_picturePath != UNUSED) {
1417         m_picturePath = properties.m_picturePath;
1418     }
1419     if (properties.m_bulletColor != UNUSED) {
1420         m_bulletColor = properties.m_bulletColor;
1421     }
1422     if (properties.m_bulletRelativeSize != UNUSED) {
1423         m_bulletRelativeSize = properties.m_bulletRelativeSize;
1424     }
1425     if (properties.m_bulletSize != UNUSED) {
1426         m_bulletSize = properties.m_bulletSize;
1427     }
1428     if (properties.m_followingChar != UNUSED) {
1429         m_followingChar = properties.m_followingChar;
1430     }
1431     if (!(properties.m_textStyle == m_textStyle)) {
1432         KoGenStyle::copyPropertiesFromStyle(properties.m_textStyle, m_textStyle, KoGenStyle::TextType);
1433     }
1434 }
1435 
convertToListProperties(KoGenStyles & mainStyles,Utils::MSOOXMLFilter currentFilter)1436 QString Utils::ParagraphBulletProperties::convertToListProperties(KoGenStyles& mainStyles, Utils::MSOOXMLFilter currentFilter)
1437 {
1438     QBuffer buf;
1439     buf.open(QIODevice::WriteOnly);
1440     KoXmlWriter out(&buf);
1441 
1442     //---------------------------------------------
1443     // list-level-style-*
1444     //---------------------------------------------
1445     if (m_type == ParagraphBulletProperties::NumberType) {
1446         out.startElement("text:list-level-style-number");
1447         if (m_numFormat != UNUSED) {
1448             out.addAttribute("style:num-format", m_numFormat);
1449         }
1450         if (m_prefix != UNUSED) {
1451             out.addAttribute("style:num-prefix", m_prefix);
1452         }
1453         if (m_suffix != UNUSED) {
1454             out.addAttribute("style:num-suffix", m_suffix);
1455         }
1456         out.addAttribute("text:start-value", m_startValue);
1457     }
1458     else if (m_type == ParagraphBulletProperties::PictureType) {
1459         out.startElement("text:list-level-style-image");
1460         out.addAttribute("xlink:href", m_picturePath);
1461         out.addAttribute("xlink:type", "simple");
1462         out.addAttribute("xlink:show", "embed");
1463         out.addAttribute("xlink:actuate", "onLoad");
1464     }
1465     else {
1466         out.startElement("text:list-level-style-bullet");
1467         if (m_bulletChar.length() != 1) {
1468             // TODO: if there is no bullet char this should not be
1469             // saved as list but as normal paragraph. Both LO and MSO
1470             // do export it just as paragraph and no list until there
1471             // is a fix available that change that we use a Zero Width
1472             // Space to not generate invalid xml
1473             out.addAttribute("text:bullet-char", QChar(0x200B));
1474         } else {
1475             out.addAttribute("text:bullet-char", m_bulletChar);
1476         }
1477     }
1478     out.addAttribute("text:level", m_level);
1479 
1480     //---------------------------------------------
1481     // text-properties
1482     //---------------------------------------------
1483     //
1484     // NOTE: Setting a num. of text-properties to default values if
1485     // not provided for the list style to maintain compatibility with
1486     // both ODF and MSOffice.
1487 
1488     QString bulletSize;
1489     if (m_bulletRelativeSize != UNUSED) {
1490         bulletSize = QString(m_bulletRelativeSize).append("%");
1491     } else if (m_bulletSize != UNUSED) {
1492         bulletSize = QString(m_bulletSize).append("pt");
1493     } else {
1494         bulletSize = "100%";
1495     }
1496 
1497     // MSWord: A label does NOT inherit Underline from text-properties
1498     // of the paragraph style.  A bullet does not inherit {Italics, Bold}.
1499     if (currentFilter == Utils::DocxFilter && m_type != ParagraphBulletProperties::PictureType) {
1500         if (m_type != ParagraphBulletProperties::NumberType) {
1501             if ((m_textStyle.property("fo:font-style")).isEmpty()) {
1502                 m_textStyle.addProperty("fo:font-style", "normal");
1503             }
1504             if ((m_textStyle.property("fo:font-weight")).isEmpty()) {
1505                 m_textStyle.addProperty("fo:font-weight", "normal");
1506             }
1507         }
1508         if ((m_textStyle.property("style:text-underline-style")).isEmpty()) {
1509             m_textStyle.addProperty("style:text-underline-style", "none");
1510         }
1511         //fo:font-size
1512         if ((m_textStyle.property("fo:font-size")).isEmpty()) {
1513             m_textStyle.addProperty("fo:font-size", bulletSize);
1514         }
1515         out.addAttribute("text:style-name", mainStyles.insert(m_textStyle, "T"));
1516     }
1517     //---------------------------------------------
1518     // list-level-properties
1519     //---------------------------------------------
1520     out.startElement("style:list-level-properties");
1521     if (m_align != UNUSED) {
1522         out.addAttribute("fo:text-align", m_align);
1523     }
1524     if ((m_type == ParagraphBulletProperties::PictureType) && (m_bulletSize != UNUSED)) {
1525         QString size = QString(m_bulletSize).append("pt");
1526         out.addAttribute("fo:width", size);
1527         out.addAttribute("fo:height", size);
1528     }
1529 
1530     out.addAttribute("text:list-level-position-and-space-mode", "label-alignment");
1531     // NOTE: DrawingML: If indent and marL were not provided by a master slide
1532     // or defaults, then according to the spec. a value of -342900 is implied
1533     // for indent and a value of 347663 is implied for marL (no matter which
1534     // level and which type of text).  However the result is not compliant with
1535     // MS PowerPoint => using ZERO values as in the ppt filter.
1536     double margin = 0;
1537     double indent = 0;
1538     bool ok = false;
1539 
1540     if (m_margin != UNUSED) {
1541         margin = m_margin.toDouble(&ok);
1542         if (!ok) {
1543             debugMsooXml << "STRING_TO_DOUBLE: error converting" << m_margin << "(attribute \"marL\")";
1544         }
1545     }
1546     if (m_indent != UNUSED) {
1547         indent = m_indent.toDouble(&ok);
1548         if (!ok) {
1549             debugMsooXml << "STRING_TO_DOUBLE: error converting" << m_indent << "(attribute \"indent\")";
1550         }
1551     }
1552     out.startElement("style:list-level-label-alignment");
1553 
1554     if (currentFilter == Utils::PptxFilter) {
1555         //fo:margin-left
1556         out.addAttributePt("fo:margin-left", margin);
1557 
1558         if (((m_type == ParagraphBulletProperties::BulletType) && m_bulletChar.isEmpty()) ||
1559             (m_type == ParagraphBulletProperties::DefaultType))
1560         {
1561             //hanging:
1562             if (indent < 0) {
1563                 if (qAbs(indent) > margin) {
1564                     out.addAttributePt("fo:text-indent", -margin);
1565                 } else {
1566                     out.addAttributePt("fo:text-indent", indent);
1567                 }
1568             }
1569             //first-line and none:
1570             else {
1571                 out.addAttributePt("fo:text-indent", indent);
1572             }
1573             out.addAttribute("text:label-followed-by", "nothing");
1574         } else {
1575             //hanging:
1576             if (indent < 0) {
1577                 if (qAbs(indent) > margin) {
1578                     out.addAttributePt("fo:text-indent", -margin);
1579                     out.addAttribute("text:label-followed-by", "listtab");
1580                     out.addAttributePt("text:list-tab-stop-position", qAbs(indent));
1581                 } else {
1582                     out.addAttributePt("fo:text-indent", indent);
1583                     out.addAttribute("text:label-followed-by", "listtab");
1584                     out.addAttributePt("text:list-tab-stop-position", margin);
1585                 }
1586             }
1587             //first-line:
1588             else if (indent > 0) {
1589                 out.addAttribute("fo:text-indent", "0pt");
1590                 out.addAttribute("text:label-followed-by", "listtab");
1591                 out.addAttributePt("text:list-tab-stop-position", margin + indent);
1592             }
1593             //none
1594             else {
1595                 out.addAttribute("fo:text-indent", "0pt");
1596                 out.addAttribute("text:label-followed-by", "nothing");
1597             }
1598         }
1599     } else {
1600         //fo:margin-left
1601         out.addAttributePt("fo:margin-left", margin);
1602         //fo:text-indent
1603         out.addAttributePt("fo:text-indent", indent);
1604         //text:label-followed-by
1605         if ((m_followingChar == "tab") || (m_followingChar == UNUSED)) {
1606             out.addAttribute("text:label-followed-by", "listtab");
1607             // Layout hints: none/first-line/hanging are values from the
1608             // Special field of the Paragraph dialog in MS Word.
1609             //
1610             // first-line:
1611             // IF (indent > 0) and (margin > 0), THEN use default tab stop OR a custom tab stop if defined.
1612             // IF (indent > 0) and (margin == 0), THEN use default tab stop OR a custom tab stop if defined.
1613             // IF (indent > 0) and (margin < 0), THEN use default tab stop OR a custom tab stop if defined.
1614             //
1615             // none:
1616             // IF (indent == 0) and (margin > 0), THEN use default tab stop OR a custom tab stop if defined.
1617             // IF (indent == 0) and (margin == 0), THEN use default tab stop OR a custom tab stop if defined.
1618             // IF (indent == 0) and (margin < 0), THEN use default tab stop OR a custom tab stop if defined.
1619             //
1620             // hanging:
1621             // 1. the tab should be placed at the margin position
1622             // 2. bullet_position = margin - indent; (that's the indentation
1623             // left value that can be seen in Paragraph dialog in MS Word)
1624         }
1625         //space and nothing are same in OOXML and ODF
1626         else {
1627             out.addAttribute("text:label-followed-by", m_followingChar);
1628         }
1629     }
1630     out.endElement(); //style:list-level-label-alignment
1631     out.endElement(); //style:list-level-properties
1632     if (currentFilter != Utils::DocxFilter && m_type != ParagraphBulletProperties::PictureType) {
1633         out.startElement("style:text-properties");
1634         if (m_bulletColor != UNUSED) {
1635             out.addAttribute("fo:color", m_bulletColor);
1636         }
1637         out.addAttribute("fo:font-size", bulletSize);
1638 
1639         //MSPowerPoint: UI does not enable to change font of a numbered lists.
1640         if (m_bulletFont != UNUSED) {
1641             if ((currentFilter != Utils::PptxFilter) || (m_type == ParagraphBulletProperties::BulletType)) {
1642                 out.addAttribute("fo:font-family", m_bulletFont);
1643             }
1644         }
1645         //MSPowerPoint: A label does NOT inherit Underline from text-properties
1646         //of the 1st text chunk.  A bullet does NOT inherit {Italics, Bold}.
1647         if (currentFilter == Utils::PptxFilter) {
1648             if (m_type != ParagraphBulletProperties::NumberType) {
1649                 out.addAttribute("fo:font-style", "normal");
1650                 out.addAttribute("fo:font-weight", "normal");
1651             }
1652             out.addAttribute("style:text-underline-style", "none");
1653         }
1654         out.endElement(); //style:text-properties
1655     }
1656     out.endElement(); //text:list-level-style-*
1657 
1658     return QString::fromUtf8(buf.buffer(), buf.buffer().size());
1659 }
1660 
1661