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