File indexing completed on 2024-05-12 16:29:12

0001 /*
0002  * This file is part of Office 2007 Filters for Calligra
0003  * Copyright (C) 2002 Laurent Montel <lmontel@mandrakesoft.com>
0004  * Copyright (c) 2003 Lukas Tinkl <lukas@kde.org>
0005  * Copyright (C) 2003 David Faure <faure@kde.org>
0006  * Copyright (C) 2009-2010 Nokia Corporation and/or its subsidiary(-ies).
0007  * Contact: Suresh Chande suresh.chande@nokia.com
0008  * Copyright (C) 2011 Matus Uzak <matus.uzak@ixonos.com>
0009  *
0010  * Utils::columnName() based on Cell::columnName() from calligra/kspread/Utils.cpp:
0011  * Copyright 2006-2007 Stefan Nikolaus <stefan.nikolaus@kdemail.net>
0012  * Copyright 2004 Tomas Mecir <mecirt@gmail.com>
0013  * Copyright 1999-2002,2004 Laurent Montel <montel@kde.org>
0014  * Copyright 2002,2004 Ariya Hidayat <ariya@kde.org>
0015  * Copyright 2002-2003 Norbert Andres <nandres@web.de>
0016  * Copyright 2003 Stefan Hetzl <shetzl@chello.at>
0017  * Copyright 2001-2002 Philipp Mueller <philipp.mueller@gmx.de>
0018  * Copyright 2002 Harri Porten <porten@kde.org>
0019  * Copyright 2002 John Dailey <dailey@vt.edu>
0020  * Copyright 1999-2001 David Faure <faure@kde.org>
0021  * Copyright 2000-2001 Werner Trobin <trobin@kde.org>
0022  * Copyright 2000 Simon Hausmann <hausmann@kde.org
0023  * Copyright 1998-1999 Torben Weis <weis@kde.org>
0024  * Copyright 1999 Michael Reiher <michael.reiher@gmx.de>
0025  * Copyright 1999 Reginald Stadlbauer <reggie@kde.org>
0026  *
0027  * This library is free software; you can redistribute it and/or
0028  * modify it under the terms of the GNU Lesser General Public License
0029  * version 2.1 as published by the Free Software Foundation.
0030  *
0031  * This library is distributed in the hope that it will be useful, but
0032  * WITHOUT ANY WARRANTY; without even the implied warranty of
0033  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
0034  * Lesser General Public License for more details.
0035  *
0036  * You should have received a copy of the GNU Lesser General Public
0037  * License along with this library; if not, write to the Free Software
0038  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
0039  * 02110-1301 USA
0040  *
0041  */
0042 
0043 #include "MsooXmlUtils.h"
0044 #include "MsooXmlUnits.h"
0045 #include "MsooXmlContentTypes.h"
0046 #include "MsooXmlSchemas.h"
0047 #include "MsooXmlReader.h"
0048 #include "MsooXmlDebug.h"
0049 
0050 #include "ooxml_pole.h"
0051 
0052 #include <styles/KoCharacterStyle.h>
0053 #include <KoXmlReader.h>
0054 #include <KoXmlWriter.h>
0055 #include <KoGenStyles.h>
0056 #include <KoUnit.h>
0057 
0058 #include <klocalizedstring.h>
0059 #include <kzip.h>
0060 
0061 #include <QGlobalStatic>
0062 #include <QDomDocument>
0063 #include <QColor>
0064 #include <QBrush>
0065 #include <QImage>
0066 #include <QImageReader>
0067 #include <QPalette>
0068 #include <QRegExp>
0069 
0070 
0071 #include <memory>
0072 
0073 // common officedocument content types
0074 const char MSOOXML::ContentTypes::coreProps[] =            "application/vnd.openxmlformats-package.core-properties+xml";
0075 const char MSOOXML::ContentTypes::extProps[] =             "application/vnd.openxmlformats-officedocument.extended-properties+xml";
0076 const char MSOOXML::ContentTypes::theme[] =                "application/vnd.openxmlformats-officedocument.theme+xml";
0077 
0078 // wordprocessingml-specific content types
0079 const char MSOOXML::ContentTypes::wordDocument[] =         "application/vnd.openxmlformats-officedocument.wordprocessingml.document.main+xml";
0080 const char MSOOXML::ContentTypes::wordSettings[] =         "application/vnd.openxmlformats-officedocument.wordprocessingml.settings+xml";
0081 const char MSOOXML::ContentTypes::wordStyles[] =           "application/vnd.openxmlformats-officedocument.wordprocessingml.styles+xml";
0082 const char MSOOXML::ContentTypes::wordHeader[] =           "application/vnd.openxmlformats-officedocument.wordprocessingml.header+xml";
0083 const char MSOOXML::ContentTypes::wordFooter[] =           "application/vnd.openxmlformats-officedocument.wordprocessingml.footer+xml";
0084 const char MSOOXML::ContentTypes::wordFootnotes[] =        "application/vnd.openxmlformats-officedocument.wordprocessingml.footnotes+xml";
0085 const char MSOOXML::ContentTypes::wordEndnotes[] =         "application/vnd.openxmlformats-officedocument.wordprocessingml.endnotes+xml";
0086 const char MSOOXML::ContentTypes::wordFontTable[] =        "application/vnd.openxmlformats-officedocument.wordprocessingml.fontTable+xml";
0087 const char MSOOXML::ContentTypes::wordWebSettings[] =      "application/vnd.openxmlformats-officedocument.wordprocessingml.webSettings+xml";
0088 const char MSOOXML::ContentTypes::wordTemplate[] =         "application/vnd.openxmlformats-officedocument.wordprocessingml.template.main+xml";
0089 const char MSOOXML::ContentTypes::wordComments[] =         "application/vnd.openxmlformats-officedocument.wordprocessingml.comments+xml";
0090 
0091 // presentationml-specific content types
0092 const char MSOOXML::ContentTypes::presentationDocument[] =      "application/vnd.openxmlformats-officedocument.presentationml.presentation.main+xml";
0093 const char MSOOXML::ContentTypes::presentationSlide[] =         "application/vnd.openxmlformats-officedocument.presentationml.slide+xml";
0094 const char MSOOXML::ContentTypes::presentationSlideLayout[] =   "application/vnd.openxmlformats-officedocument.presentationml.slideLayout+xml";
0095 const char MSOOXML::ContentTypes::presentationSlideShow[] =     "application/vnd.openxmlformats-officedocument.presentationml.slideshow.main+xml";
0096 const char MSOOXML::ContentTypes::presentationTemplate[] =      "application/vnd.openxmlformats-officedocument.presentationml.template.main+xml";
0097 const char MSOOXML::ContentTypes::presentationNotes[] =         "application/vnd.openxmlformats-officedocument.presentationml.notesMaster+xml";
0098 const char MSOOXML::ContentTypes::presentationTableStyles[] =   "application/vnd.openxmlformats-officedocument.presentationml.tableStyles+xml";
0099 const char MSOOXML::ContentTypes::presentationProps[] =         "application/vnd.openxmlformats-officedocument.presentationml.presProps+xml";
0100 const char MSOOXML::ContentTypes::presentationViewProps[] =     "application/vnd.openxmlformats-officedocument.presentationml.viewProps+xml";
0101 const char MSOOXML::ContentTypes::presentationComments[] =      "application/vnd.openxmlformats-officedocument.presentationml.comments+xml";
0102 
0103 // spreadsheetml-specific content types
0104 const char MSOOXML::ContentTypes::spreadsheetDocument[] =        "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml";
0105 const char MSOOXML::ContentTypes::spreadsheetMacroDocument[] =   "application/vnd.ms-excel.sheet.macroEnabled.main+xml";
0106 const char MSOOXML::ContentTypes::spreadsheetPrinterSettings[] = "application/vnd.openxmlformats-officedocument.spreadsheetml.printerSettings";
0107 const char MSOOXML::ContentTypes::spreadsheetStyles[] =          "application/vnd.openxmlformats-officedocument.spreadsheetml.styles+xml";
0108 const char MSOOXML::ContentTypes::spreadsheetWorksheet[] =       "application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml";
0109 const char MSOOXML::ContentTypes::spreadsheetCalcChain[] =       "application/vnd.openxmlformats-officedocument.spreadsheetml.calcChain+xml";
0110 const char MSOOXML::ContentTypes::spreadsheetSharedStrings[] =   "application/vnd.openxmlformats-officedocument.spreadsheetml.sharedStrings+xml";
0111 const char MSOOXML::ContentTypes::spreadsheetTemplate[] =        "application/vnd.openxmlformats-officedocument.spreadsheetml.template.main+xml";
0112 const char MSOOXML::ContentTypes::spreadsheetComments[] =        "application/vnd.openxmlformats-officedocument.spreadsheetml.comments+xml";
0113 
0114 //generic namespaces
0115 const char MSOOXML::Schemas::dublin_core[] =                                "http://purl.org/dc/elements/1.1/";
0116 
0117 // common namespaces
0118 const char MSOOXML::Schemas::contentTypes[] =                               "http://schemas.openxmlformats.org/package/2006/content-types";
0119 
0120 const char MSOOXML::Schemas::relationships[] =                              "http://schemas.openxmlformats.org/package/2006/relationships";
0121 const char MSOOXML::Schemas::core_properties[] =                            "http://schemas.openxmlformats.org/package/2006/metadata/core-properties";
0122 
0123 // ISO/IEC 29500-1:2008(E), Annex A. (normative), p. 4355
0124 // See also: specs/all.xsd
0125 // A.1 WordprocessingML
0126 const char MSOOXML::Schemas::wordprocessingml[] =                           "http://schemas.openxmlformats.org/wordprocessingml/2006/main";
0127 
0128 // A.2 SpreadsheetML
0129 const char MSOOXML::Schemas::spreadsheetml[] =                              "http://schemas.openxmlformats.org/spreadsheetml/2006/main";
0130 
0131 // A.3 PresentationML
0132 const char MSOOXML::Schemas::presentationml[] =                             "http://schemas.openxmlformats.org/presentationml/2006/main";
0133 
0134 // A.4 DrawingML - Framework
0135 const char MSOOXML::Schemas::drawingml::main[] =                            "http://schemas.openxmlformats.org/drawingml/2006/main";
0136 const char MSOOXML::Schemas::drawingml::wordprocessingDrawing[] =           "http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing";
0137 const char MSOOXML::Schemas::drawingml::spreadsheetDrawing[] =              "http://schemas.openxmlformats.org/drawingml/2006/spreadsheetDrawing";
0138 const char MSOOXML::Schemas::drawingml::compatibility[] =                   "http://schemas.openxmlformats.org/drawingml/2006/compatibility";
0139 const char MSOOXML::Schemas::drawingml::lockedCanvas[] =                    "http://schemas.openxmlformats.org/drawingml/2006/lockedCanvas";
0140 const char MSOOXML::Schemas::drawingml::picture[] =                         "http://schemas.openxmlformats.org/drawingml/2006/picture";
0141 
0142 // A.5 DrawingML - Components
0143 const char MSOOXML::Schemas::drawingml::chart[] =                           "http://schemas.openxmlformats.org/drawingml/2006/chart";
0144 const char MSOOXML::Schemas::drawingml::chartDrawing[] =                    "http://schemas.openxmlformats.org/drawingml/2006/chartDrawing";
0145 const char MSOOXML::Schemas::drawingml::diagram[] =                         "http://schemas.openxmlformats.org/drawingml/2006/diagram";
0146 
0147 // A.6 Shared MLs
0148 const char MSOOXML::Schemas::officeDocument::math[] =                       "http://schemas.openxmlformats.org/officeDocument/2006/math";
0149 const char MSOOXML::Schemas::officeDocument::bibliography[] =               "http://schemas.openxmlformats.org/officeDocument/2006/bibliography";
0150 const char MSOOXML::Schemas::officeDocument::characteristics[] =            "http://schemas.openxmlformats.org/officeDocument/2006/characteristics";
0151 const char MSOOXML::Schemas::officeDocument::customXml[] =                  "http://schemas.openxmlformats.org/officeDocument/2006/customXml";
0152 const char MSOOXML::Schemas::officeDocument::custom_properties[] =          "http://schemas.openxmlformats.org/officeDocument/2006/custom-properties";
0153 const char MSOOXML::Schemas::officeDocument::docPropsVTypes[] =             "http://schemas.openxmlformats.org/officeDocument/2006/docPropsVTypes";
0154 const char MSOOXML::Schemas::officeDocument::extended_properties[] =        "http://schemas.openxmlformats.org/officeDocument/2006/extended-properties";
0155 const char MSOOXML::Schemas::officeDocument::relationships[] =              "http://schemas.openxmlformats.org/officeDocument/2006/relationships";
0156 const char MSOOXML::Schemas::officeDocument::sharedTypes[] =                "http://schemas.openxmlformats.org/officeDocument/2006/sharedTypes";
0157 
0158 // A.7 Custom XML Schema References
0159 const char MSOOXML::Schemas::schemaLibrary[] =                              "http://schemas.openxmlformats.org/schemaLibrary/2006/main";
0160 
0161 // Marks that the value has not been modified;
0162 static const char UNUSED[] = "UNUSED";
0163 
0164 using namespace MSOOXML;
0165 
0166 //-----------------------------------------
0167 
0168 KoFilter::ConversionStatus Utils::loadAndParse(QIODevice* io, KoXmlDocument& doc,
0169         QString& errorMessage, const QString & fileName)
0170 {
0171     errorMessage.clear();
0172 
0173     QString errorMsg;
0174     int errorLine, errorColumn;
0175     bool ok = doc.setContent(io, true, &errorMsg, &errorLine, &errorColumn);
0176     if (!ok) {
0177         errorMsooXml << "Parsing error in " << fileName << ", aborting!" << endl
0178         << " In line: " << errorLine << ", column: " << errorColumn << endl
0179         << " Error message: " << errorMsg;
0180         errorMessage = i18n("Parsing error in the main document at line %1, column %2.\nError message: %3", errorLine , errorColumn , errorMsg);
0181         return KoFilter::ParsingError;
0182     }
0183     debugMsooXml << "File" << fileName << "loaded and parsed.";
0184     return KoFilter::OK;
0185 }
0186 
0187 KoFilter::ConversionStatus Utils::loadAndParse(KoXmlDocument& doc, const KZip* zip,
0188         QString& errorMessage, const QString& fileName)
0189 {
0190     errorMessage.clear();
0191     KoFilter::ConversionStatus status;
0192     std::auto_ptr<QIODevice> device(openDeviceForFile(zip, errorMessage, fileName, status));
0193     if (!device.get())
0194         return status;
0195     return loadAndParse(device.get(), doc, errorMessage, fileName);
0196 }
0197 
0198 KoFilter::ConversionStatus Utils::loadAndParseDocument(MsooXmlReader* reader,
0199         const KZip* zip,
0200         KoOdfWriters *writers,
0201         QString& errorMessage,
0202         const QString& fileName,
0203         MsooXmlReaderContext* context)
0204 {
0205     Q_UNUSED(writers)
0206     errorMessage.clear();
0207     KoFilter::ConversionStatus status;
0208     std::auto_ptr<QIODevice> device(openDeviceForFile(zip, errorMessage, fileName, status));
0209     if (!device.get())
0210         return status;
0211     reader->setDevice(device.get());
0212     reader->setFileName(fileName); // for error reporting
0213     status = reader->read(context);
0214     if (status != KoFilter::OK) {
0215         errorMessage = reader->errorString();
0216         return status;
0217     }
0218     debugMsooXml << "File" << fileName << "loaded and parsed.";
0219     return KoFilter::OK;
0220 }
0221 
0222 QIODevice* Utils::openDeviceForFile(const KZip* zip, QString& errorMessage, const QString& fileName,
0223                                     KoFilter::ConversionStatus& status)
0224 {
0225     debugMsooXml << "Trying to open" << fileName;
0226     errorMessage.clear();
0227     const KArchiveEntry* entry = zip->directory()->entry(fileName);
0228     if (!entry) {
0229         errorMessage = i18n("Entry '%1' not found.", fileName);
0230         debugMsooXml << errorMessage;
0231         status = KoFilter::FileNotFound;
0232         return 0;
0233     }
0234     if (!entry->isFile()) {
0235         errorMessage = i18n("Entry '%1' is not a file.", fileName);
0236         debugMsooXml << errorMessage;
0237         status = KoFilter::WrongFormat;
0238         return 0;
0239     }
0240     const KZipFileEntry* f = static_cast<const KZipFileEntry *>(entry);
0241     debugMsooXml << "Entry" << fileName << "has size" << f->size();
0242     status = KoFilter::OK;
0243     // There seem to be some problems with kde/zlib when trying to read
0244     // multiple streams, this functionality is needed in the filter
0245     // Until there's another solution for this, this avoids the problem
0246     //return f->createDevice();
0247     QBuffer *device = new QBuffer();
0248     device->setData(f->data());
0249     device->open(QIODevice::ReadOnly);
0250     return device;
0251 }
0252 
0253 #define BLOCK_SIZE 4096
0254 static KoFilter::ConversionStatus copyOle(QString& errorMessage,
0255                                     const QString sourceName, KoStore *outputStore,
0256                                     const QString& destinationName, const KZip* zip)
0257 {
0258     KoFilter::ConversionStatus status = KoFilter::OK;
0259 
0260     QIODevice* inputDevice = Utils::openDeviceForFile(zip, errorMessage, sourceName, status);
0261     if (!inputDevice) {
0262         // Source did not exist
0263         return KoFilter::CreationError;
0264     }
0265     inputDevice->open(QIODevice::ReadOnly);
0266 
0267     OOXML_POLE::Storage storage(inputDevice);
0268     if (!storage.open()) {
0269         debugMsooXml << "Cannot open " << sourceName;
0270         return KoFilter::WrongFormat;
0271     }
0272 
0273     std::list<std::string> lista = storage.entries();
0274     std::string oleType = "Contents";
0275 
0276     for (std::list<std::string>::iterator it = lista.begin(); it != lista.end(); ++it)  {
0277         //debugMsooXml << "ENTRY " << (*it).c_str();
0278         if (QString((*it).c_str()).contains("Ole10Native")) {
0279             oleType = "Ole10Native";
0280         }
0281         else if (QString((*it).c_str()).contains("CONTENTS")) {
0282             oleType = "CONTENTS";
0283         }
0284     }
0285 
0286     OOXML_POLE::Stream stream(&storage, oleType);
0287     QByteArray array;
0288     array.resize(stream.size());
0289 
0290     unsigned long r = stream.read((unsigned char*)array.data(), stream.size());
0291     if (r != stream.size()) {
0292         errorMsooXml << "Error while reading from stream";
0293         return KoFilter::WrongFormat;
0294     }
0295 
0296     if (oleType == "Contents" || oleType == "Ole10Native") {
0297      // Removing first 4 bytes which are the size
0298         array = array.right(array.length() - 4);
0299     }
0300 
0301     // Uncomment to write any ole file for testing
0302     //POLE::Stream streamTemp(&storage, "Ole");
0303     //QByteArray arrayTemp;
0304     //arrayTemp.resize(streamTemp.size());
0305     //streamTemp.read((unsigned char*)arrayTemp.data(), streamTemp.size());
0306     //QFile file("olething.ole");
0307     //file.open(QIODevice::WriteOnly);
0308     //QDataStream out(&file);
0309     //out.writeRawData(arrayTemp.data(), arrayTemp.length());
0310 
0311     debugMsooXml << "mode:" << outputStore->mode();
0312     if (!outputStore->open(destinationName)) {
0313         errorMessage = i18n("Could not open entry \"%1\" for writing.", destinationName);
0314         return KoFilter::CreationError;
0315     }
0316 
0317     QByteArray array2;
0318     while (true) {
0319         array2 = array.left(BLOCK_SIZE);
0320         array = array.right(array.size() - array2.size());
0321         const qint64 in = array2.size();
0322         if (in <= 0) {
0323             break;
0324         }
0325         char *block = array2.data();
0326         if (in != outputStore->write(block, in)) {
0327             errorMessage = i18n("Could not write block");
0328             status = KoFilter::CreationError;
0329             break;
0330         }
0331     }
0332     outputStore->close();
0333     delete inputDevice;
0334     inputDevice = 0;
0335     return status;
0336 }
0337 #undef BLOCK_SIZE
0338 
0339 #define BLOCK_SIZE 4096
0340 KoFilter::ConversionStatus Utils::createImage(QString& errorMessage,
0341                                        const QImage& source, KoStore *outputStore,
0342                                        const QString& destinationName)
0343 {
0344     if (outputStore->hasFile(destinationName)) {
0345         return KoFilter::OK;
0346     }
0347 
0348     KoFilter::ConversionStatus status = KoFilter::OK;
0349     QByteArray array;
0350     QBuffer inputDevice(&array);
0351     inputDevice.open(QIODevice::ReadWrite);
0352     QFileInfo info = QFileInfo(destinationName);
0353     source.save(&inputDevice, info.suffix().toUtf8());
0354     inputDevice.seek(0);
0355 
0356     if (!outputStore->open(destinationName)) {
0357         errorMessage = i18n("Could not open entry \"%1\" for writing.", destinationName);
0358         return KoFilter::CreationError;
0359     }
0360     char block[BLOCK_SIZE];
0361     while (true) {
0362         const qint64 in = inputDevice.read(block, BLOCK_SIZE);
0363         if (in <= 0) {
0364             break;
0365         }
0366         if (in != outputStore->write(block, in)) {
0367             errorMessage = i18n("Could not write block");
0368             status = KoFilter::CreationError;
0369             break;
0370         }
0371     }
0372     outputStore->close();
0373     return status;
0374 }
0375 #undef BLOCK_SIZE
0376 
0377 #define BLOCK_SIZE 4096
0378 KoFilter::ConversionStatus Utils::copyFile(const KZip* zip, QString& errorMessage,
0379                                            const QString& sourceName, KoStore *outputStore,
0380                                            const QString& destinationName, bool oleType)
0381 {
0382     if (outputStore->hasFile(destinationName)) {
0383         return KoFilter::OK;
0384     }
0385 
0386     KoFilter::ConversionStatus status;
0387     if (oleType) {
0388         status = copyOle(errorMessage, sourceName, outputStore, destinationName, zip);
0389         return status;
0390     }
0391 
0392     std::auto_ptr<QIODevice> inputDevice = std::auto_ptr<QIODevice>(Utils::openDeviceForFile(zip, errorMessage, sourceName, status));
0393 
0394     if (!inputDevice.get()) {
0395         return status;
0396     }
0397 
0398     debugMsooXml << "mode:" << outputStore->mode();
0399     if (!outputStore->open(destinationName)) {
0400         errorMessage = i18n("Could not open entry \"%1\" for writing.", destinationName);
0401         return KoFilter::CreationError;
0402     }
0403     status = KoFilter::OK;
0404     char block[BLOCK_SIZE];
0405     while (true) {
0406         const qint64 in = inputDevice->read(block, BLOCK_SIZE);
0407 //        debugMsooXml << "in:" << in;
0408         if (in <= 0)
0409             break;
0410         if (in != outputStore->write(block, in)) {
0411             errorMessage = i18n("Could not write block");
0412             status = KoFilter::CreationError;
0413             break;
0414         }
0415     }
0416     outputStore->close();
0417     return status;
0418 }
0419 #undef BLOCK_SIZE
0420 
0421 KoFilter::ConversionStatus Utils::imageSize(const KZip* zip, QString& errorMessage, const QString& sourceName,
0422                                             QSize* size)
0423 {
0424     Q_ASSERT(size);
0425     KoFilter::ConversionStatus status;
0426     std::auto_ptr<QIODevice> inputDevice(Utils::openDeviceForFile(zip, errorMessage, sourceName, status));
0427     if (!inputDevice.get()) {
0428         return status;
0429     }
0430     QImageReader r(inputDevice.get(), QFileInfo(sourceName).suffix().toLatin1());
0431     if (!r.canRead())
0432         return KoFilter::WrongFormat;
0433     *size = r.size();
0434     debugMsooXml << *size;
0435     return KoFilter::OK;
0436 }
0437 
0438 KoFilter::ConversionStatus Utils::loadThumbnail(QImage& thumbnail, KZip* zip)
0439 {
0440 //! @todo
0441     Q_UNUSED(thumbnail)
0442     Q_UNUSED(zip)
0443     return KoFilter::FileNotFound;
0444 }
0445 
0446 //! @return true if @a el has tag name is equal to @a expectedTag or false otherwise;
0447 //!         on failure optional @a warningPrefix message is prepended to the warning
0448 static bool checkTag(const KoXmlElement& el, const char* expectedTag, const char* warningPrefix = 0)
0449 {
0450     if (el.tagName() != expectedTag) {
0451         warnMsooXml
0452         << (warningPrefix ? QString::fromLatin1(warningPrefix) + ":" : QString())
0453         << "tag name=" << el.tagName() << " expected:" << expectedTag;
0454         return false;
0455     }
0456     return true;
0457 }
0458 
0459 //! @return true if @a el has namespace URI is equal to @a expectedNSURI or false otherwise
0460 static bool checkNsUri(const KoXmlElement& el, const char* expectedNsUri)
0461 {
0462     if (el.namespaceURI() != expectedNsUri) {
0463         warnMsooXml << "Invalid namespace URI" << el.namespaceURI() << " expected:" << expectedNsUri;
0464         return false;
0465     }
0466     return true;
0467 }
0468 
0469 bool Utils::convertBooleanAttr(const QString& value, bool defaultValue)
0470 {
0471     const QByteArray val(value.toLatin1());
0472     if (val.isEmpty()) {
0473         return defaultValue;
0474     }
0475     debugMsooXml << val;
0476 
0477     return val != MsooXmlReader::constOff && val != MsooXmlReader::constFalse && val != MsooXmlReader::const0;
0478 }
0479 
0480 KoFilter::ConversionStatus Utils::loadContentTypes(
0481     const KoXmlDocument& contentTypesXML, QMultiHash<QByteArray, QByteArray>& contentTypes)
0482 {
0483     KoXmlElement typesEl(contentTypesXML.documentElement());
0484     if (!checkTag(typesEl, "Types", "documentElement")) {
0485         return KoFilter::WrongFormat;
0486     }
0487     if (!checkNsUri(typesEl, Schemas::contentTypes)) {
0488         return KoFilter::WrongFormat;
0489     }
0490     KoXmlElement e;
0491     forEachElement(e, typesEl) {
0492         const QString tagName(e.tagName());
0493         if (!checkNsUri(e, Schemas::contentTypes)) {
0494             return KoFilter::WrongFormat;
0495         }
0496 
0497         if (tagName == "Override") {
0498             //ContentType -> PartName mapping
0499             const QByteArray atrPartName(e.attribute("PartName").toLatin1());
0500             const QByteArray atrContentType(e.attribute("ContentType").toLatin1());
0501             if (atrPartName.isEmpty() || atrContentType.isEmpty()) {
0502                 warnMsooXml << "Invalid data for" << tagName
0503                 << "element: PartName=" << atrPartName << "ContentType=" << atrContentType;
0504                 return KoFilter::WrongFormat;
0505             }
0506 //debugMsooXml << atrContentType << "->" << atrPartName;
0507             contentTypes.insert(atrContentType, atrPartName);
0508         } else if (tagName == "Default") {
0509 //! @todo
0510             // skip for now...
0511         }
0512     }
0513     return KoFilter::OK;
0514 }
0515 
0516 KoFilter::ConversionStatus Utils::loadDocumentProperties(const KoXmlDocument& appXML, QMap<QString, QVariant>& properties)
0517 {
0518     KoXmlElement typesEl(appXML.documentElement());
0519     KoXmlElement e, elem, element;
0520     forEachElement(element, typesEl) {
0521         QVariant v;
0522         forEachElement(elem, element) {
0523             if(elem.tagName() == "vector") {
0524                 QVariantList list;
0525                 forEachElement(e, elem)
0526                     list.append(e.text());
0527                 v = list;
0528             }
0529         }
0530         if(!v.isValid())
0531             v = element.text();
0532         properties[element.tagName()] = v;
0533     }
0534     return KoFilter::OK;
0535 }
0536 
0537 bool Utils::ST_Lang_to_languageAndCountry(const QString& value, QString& language, QString& country)
0538 {
0539     int indexForCountry =  value.indexOf('-');
0540     if (indexForCountry <= 0)
0541         return false;
0542     indexForCountry++;
0543     language = value.left(indexForCountry - 1);
0544     country = value.mid(indexForCountry);
0545     return !country.isEmpty();
0546 }
0547 
0548 class ST_HighlightColorMapping : public QHash<QString, QColor>
0549 {
0550 public:
0551     ST_HighlightColorMapping() {
0552 #define INSERT_HC(c, hex) insert(QLatin1String(c), QColor( QRgb( 0xff000000 | hex ) ) )
0553         INSERT_HC("black", 0x000000);
0554         INSERT_HC("blue", 0x0000ff);
0555         INSERT_HC("cyan", 0x00ffff);
0556         INSERT_HC("darkBlue", 0x000080);
0557         INSERT_HC("darkCyan", 0x008080);
0558         INSERT_HC("darkGray", 0x808080);
0559         INSERT_HC("darkGreen", 0x008000);
0560         INSERT_HC("darkMagenta", 0x800080);
0561         INSERT_HC("darkRed", 0x800000);
0562         INSERT_HC("darkYellow", 0x808000);
0563         INSERT_HC("green", 0x00ff00);
0564         INSERT_HC("lightGray", 0xc0c0c0);
0565         INSERT_HC("magenta", 0xff00ff);
0566         INSERT_HC("red", 0xff0000);
0567         INSERT_HC("yellow", 0xffff00);
0568         INSERT_HC("white", 0xffffff);
0569 #undef INSERT_HC
0570     }
0571 };
0572 
0573 Q_GLOBAL_STATIC(ST_HighlightColorMapping, s_ST_HighlightColor_to_QColor)
0574 
0575 QBrush Utils::ST_HighlightColor_to_QColor(const QString& colorName)
0576 {
0577     const QColor c(s_ST_HighlightColor_to_QColor->value(colorName));
0578     if (c.isValid())
0579         return QBrush(c);
0580     return QBrush(); // for "none" or anything unsupported
0581 }
0582 
0583 qreal Utils::ST_Percentage_to_double(const QString& val, bool& ok)
0584 {
0585     if (!val.endsWith('%')) {
0586         ok = false;
0587         return 0.0;
0588     }
0589     QString result(val);
0590     result.truncate(1);
0591     return result.toDouble(&ok);
0592 }
0593 
0594 qreal Utils::ST_Percentage_withMsooxmlFix_to_double(const QString& val, bool& ok)
0595 {
0596     const qreal result = ST_Percentage_to_double(val, ok);
0597     if (ok)
0598         return result;
0599     // MSOOXML fix: the format is int({ST_Percentage}*1000)
0600     const int resultInt = val.toInt(&ok);
0601     if (!ok)
0602         return 0.0;
0603     return qreal(resultInt) / 1000.0;
0604 }
0605 
0606 QColor Utils::colorForLuminance(const QColor& color, const DoubleModifier& modulation, const DoubleModifier& offset)
0607 {
0608     if (modulation.valid) {
0609         int r, g, b;
0610         color.getRgb(&r, &g, &b);
0611         if (offset.valid) {
0612             return QColor(
0613                        int(floor((255 - r) * (100.0 - modulation.value) / 100.0 + r)),
0614                        int(floor((255 - g) * offset.value / 100.0 + g)),
0615                        int(floor((255 - b) * offset.value / 100.0 + b)),
0616                        color.alpha());
0617         } else {
0618             return QColor(
0619                        int(floor(r * modulation.value / 100.0)),
0620                        int(floor(g * modulation.value / 100.0)),
0621                        int(floor(b * modulation.value / 100.0)),
0622                        color.alpha());
0623         }
0624     }
0625     return color;
0626 }
0627 
0628 KOMSOOXML_EXPORT void Utils::modifyColor(QColor& color, qreal tint, qreal shade, qreal satMod)
0629 {
0630     int red = color.red();
0631     int green = color.green();
0632     int blue = color.blue();
0633 
0634     if (tint > 0) {
0635         red = tint * red + (1 - tint) * 255;
0636         green = tint * green + (1 - tint) * 255;
0637         blue = tint * blue + (1 - tint) * 255;
0638     }
0639     if (shade > 0) {
0640         red = shade * red;
0641         green = shade * green;
0642         blue = shade * blue;
0643     }
0644 
0645     // FIXME: This calculation for sure is incorrect,
0646     // According to MS forums, RGB should first be converted to linear RGB
0647     // Then to HSL and then multiply saturation value by satMod
0648     // SatMod can be for example 3.5 so converting RGB -> HSL is not an option
0649     // ADD INFO: MS document does not say that when calculating TINT and SHADE
0650     // That whether one should use normal RGB or linear RGB, check it!
0651 
0652 
0653     // This method is used temporarily, it seems to produce visually better results than the lower one.
0654     if (satMod > 0) {
0655         QColor temp = QColor(red, green, blue);
0656         qreal saturationFromFull = 1.0 - temp.saturationF();
0657         temp = QColor::fromHsvF(temp.hueF(), temp.saturationF() + saturationFromFull / 10 * satMod, temp.valueF());
0658         red = temp.red();
0659         green = temp.green();
0660         blue = temp.blue();
0661     }
0662 
0663     /*
0664     if (satMod > 0) {
0665         red = red * satMod;
0666         green = green * satMod;
0667         blue = blue * satMod;
0668         if (red > 255) {
0669             red = 255;
0670         }
0671         if (green > 255) {
0672             green = 255;
0673         }
0674         if (blue > 255) {
0675             blue = 255;
0676         }
0677     }
0678     */
0679 
0680     color = QColor(red, green, blue);
0681 }
0682 
0683 class ST_PlaceholderType_to_ODFMapping : public QHash<QByteArray, QByteArray>
0684 {
0685 public:
0686     ST_PlaceholderType_to_ODFMapping() {
0687         insert("body", "outline");
0688         insert("chart", "chart");
0689         insert("clipArt", "graphic");
0690         insert("ctrTitle", "title");
0691 //! @todo dgm->orgchart?
0692         insert("dgm", "orgchart");
0693         insert("dt", "date-time");
0694         insert("ftr", "footer");
0695         insert("hdr", "header");
0696 //! @todo media->object?
0697         insert("media", "object");
0698         insert("obj", "object");
0699         insert("pic", "graphic");
0700 //! @todo sldImg->graphic?
0701         insert("sldImg", "graphic");
0702         insert("sldNum", "page-number");
0703         insert("subTitle", "subtitle");
0704         insert("tbl", "table");
0705         insert("title", "title");
0706     }
0707 };
0708 
0709 Q_GLOBAL_STATIC(ST_PlaceholderType_to_ODFMapping, s_ST_PlaceholderType_to_ODF)
0710 
0711 QString Utils::ST_PlaceholderType_to_ODF(const QString& ecmaType)
0712 {
0713     QHash<QByteArray, QByteArray>::ConstIterator it(s_ST_PlaceholderType_to_ODF->constFind(ecmaType.toLatin1()));
0714     if (it == s_ST_PlaceholderType_to_ODF->constEnd())
0715         return QLatin1String("text");
0716     return QString(it.value());
0717 }
0718 
0719 //! Mapping for handling u element, used in setupUnderLineStyle()
0720 struct UnderlineStyle {
0721     UnderlineStyle(
0722         KoCharacterStyle::LineStyle style_,
0723         KoCharacterStyle::LineType type_,
0724         KoCharacterStyle::LineWeight weight_,
0725         KoCharacterStyle::LineMode mode_ = KoCharacterStyle::ContinuousLineMode)
0726             : style(style_), type(type_), weight(weight_), mode(mode_) {
0727     }
0728 
0729     KoCharacterStyle::LineStyle style;
0730     KoCharacterStyle::LineType type;
0731     KoCharacterStyle::LineWeight weight;
0732     KoCharacterStyle::LineMode mode;
0733 };
0734 
0735 typedef QHash<QByteArray, UnderlineStyle*> UnderlineStylesHashBase;
0736 
0737 class UnderlineStylesHash : public UnderlineStylesHashBase
0738 {
0739 public:
0740     UnderlineStylesHash() {
0741         // default:
0742         insert("-",
0743                new UnderlineStyle(KoCharacterStyle::SolidLine, KoCharacterStyle::SingleLine,
0744                                   KoCharacterStyle::AutoLineWeight)
0745               );
0746         // 17.18.99 ST_Underline (Underline Patterns), WML ECMA-376 p.1681:
0747         insert("single",
0748                new UnderlineStyle(KoCharacterStyle::SolidLine, KoCharacterStyle::SingleLine,
0749                                   KoCharacterStyle::AutoLineWeight)
0750               );
0751         insert("double",
0752                new UnderlineStyle(KoCharacterStyle::SolidLine, KoCharacterStyle::DoubleLine,
0753                                   KoCharacterStyle::AutoLineWeight)
0754               );
0755         insert("dbl",
0756                new UnderlineStyle(KoCharacterStyle::SolidLine, KoCharacterStyle::DoubleLine,
0757                                   KoCharacterStyle::AutoLineWeight)
0758               );
0759         insert("words",
0760                new UnderlineStyle(KoCharacterStyle::SolidLine, KoCharacterStyle::SingleLine,
0761                                   KoCharacterStyle::AutoLineWeight, KoCharacterStyle::SkipWhiteSpaceLineMode)
0762               );
0763         insert("thick",
0764                new UnderlineStyle(KoCharacterStyle::SolidLine, KoCharacterStyle::SingleLine,
0765                                   KoCharacterStyle::BoldLineWeight)
0766               );
0767         insert("dash",
0768                new UnderlineStyle(KoCharacterStyle::DashLine, KoCharacterStyle::SingleLine,
0769                                   KoCharacterStyle::AutoLineWeight)
0770               );
0771         insert("dashDotHeavy",
0772                new UnderlineStyle(KoCharacterStyle::DotDashLine, KoCharacterStyle::SingleLine,
0773                                   KoCharacterStyle::BoldLineWeight)
0774               );
0775         insert("dotted",
0776                new UnderlineStyle(KoCharacterStyle::DottedLine, KoCharacterStyle::SingleLine,
0777                                   KoCharacterStyle::AutoLineWeight)
0778               );
0779         insert("dotDash",
0780                new UnderlineStyle(KoCharacterStyle::DotDashLine, KoCharacterStyle::SingleLine,
0781                                   KoCharacterStyle::AutoLineWeight)
0782               );
0783         insert("dotDotDash",
0784                new UnderlineStyle(KoCharacterStyle::DotDotDashLine, KoCharacterStyle::SingleLine,
0785                                   KoCharacterStyle::AutoLineWeight)
0786               );
0787         insert("wave",
0788                new UnderlineStyle(KoCharacterStyle::WaveLine, KoCharacterStyle::SingleLine,
0789                                   KoCharacterStyle::AutoLineWeight)
0790               );
0791         insert("wavyDouble",
0792                new UnderlineStyle(KoCharacterStyle::WaveLine, KoCharacterStyle::DoubleLine,
0793                                   KoCharacterStyle::AutoLineWeight)
0794               );
0795         insert("wavyDbl",
0796                new UnderlineStyle(KoCharacterStyle::WaveLine, KoCharacterStyle::DoubleLine,
0797                                   KoCharacterStyle::AutoLineWeight)
0798               );
0799         insert("wavyHeavy",
0800                new UnderlineStyle(KoCharacterStyle::WaveLine, KoCharacterStyle::SingleLine,
0801                                   KoCharacterStyle::BoldLineWeight)
0802               );
0803 //! @todo more styles
0804 
0805         // 20.1.10.82 ST_TextUnderlineType (Text Underline Types), DrawingML ECMA-376 p.3450:
0806         insert("none",
0807                new UnderlineStyle(KoCharacterStyle::NoLineStyle, KoCharacterStyle::NoLineType,
0808                                   KoCharacterStyle::AutoLineWeight)
0809               );
0810         insert("sng",
0811                new UnderlineStyle(KoCharacterStyle::SolidLine, KoCharacterStyle::SingleLine,
0812                                   KoCharacterStyle::AutoLineWeight)
0813               );
0814 //! @todo more styles
0815     }
0816 
0817     ~UnderlineStylesHash() {
0818         qDeleteAll(*this);
0819     }
0820 
0821     void setup(const QString& msooxmlName,
0822                KoCharacterStyle* textStyleProperties) {
0823         UnderlineStyle* style = value(msooxmlName.toLatin1());
0824         if (!style)
0825             style = value("-");
0826         textStyleProperties->setUnderlineStyle(style->style);
0827         // add style:text-underline-type if it is not "single"
0828         if (KoCharacterStyle::SingleLine != style->type) {
0829             textStyleProperties->setUnderlineType(style->type);
0830         }
0831         textStyleProperties->setUnderlineWidth(style->weight, 1.0);
0832         // add style:text-underline-mode if it is not "continuous"
0833         if (KoCharacterStyle::ContinuousLineMode != style->mode) {
0834             textStyleProperties->setUnderlineMode(style->mode);
0835         }
0836     }
0837 };
0838 
0839 void Utils::rotateString(const qreal rotation, const qreal width, const qreal height, qreal& angle, qreal& xDiff, qreal& yDiff)
0840 {
0841     angle = -(qreal)rotation * ((qreal)(M_PI) / (qreal)180.0)/ (qreal)60000.0;
0842     //position change is calculated based on the fact that center point stays in the same location
0843     // Width/2 = Xnew + cos(angle)*Width/2 - sin(angle)*Height/2
0844     // Height/2 = Ynew + sin(angle)*Width/2 + cos(angle)*Height/2
0845     xDiff = width/2 - cos(-angle)*width/2 + sin(-angle)*height/2;
0846     yDiff = height/2 - sin(-angle)*width/2 - cos(-angle)*height/2;
0847 }
0848 
0849 
0850 Q_GLOBAL_STATIC(UnderlineStylesHash, s_underLineStyles)
0851 
0852 void Utils::setupUnderLineStyle(const QString& msooxmlName, KoCharacterStyle* textStyleProperties)
0853 {
0854     s_underLineStyles->setup(msooxmlName, textStyleProperties);
0855 }
0856 
0857 //-----------------------------------------
0858 // Marker styles
0859 //-----------------------------------------
0860 
0861 namespace
0862 {
0863     static const char* const markerStyles[6] = {
0864         "", "msArrowEnd_20_5", "msArrowStealthEnd_20_5", "msArrowDiamondEnd_20_5",
0865         "msArrowOvalEnd_20_5", "msArrowOpenEnd_20_5"
0866     };
0867 
0868     // trying to maintain compatibility with libmso
0869     enum MSOLINEEND_CUSTOM {
0870         msolineNoEnd,
0871         msolineArrowEnd,
0872         msolineArrowStealthEnd,
0873         msolineArrowDiamondEnd,
0874         msolineArrowOvalEnd,
0875         msolineArrowOpenEnd
0876     };
0877 }
0878 
0879 QString Utils::defineMarkerStyle(KoGenStyles& mainStyles, const QString& type)
0880 {
0881     uint id;
0882 
0883     if (type == "arrow") {
0884         id = msolineArrowOpenEnd;
0885     } else if (type == "stealth") {
0886         id = msolineArrowStealthEnd;
0887     } else if (type == "diamond") {
0888         id = msolineArrowDiamondEnd;
0889     } else if (type == "oval") {
0890         id = msolineArrowOvalEnd;
0891     } else if (type == "triangle") {
0892         id = msolineArrowEnd;
0893     } else {
0894         return QString();
0895     }
0896 
0897     const QString name(markerStyles[id]);
0898 
0899     if (mainStyles.style(name, "")) {
0900         return name;
0901     }
0902 
0903     KoGenStyle marker(KoGenStyle::MarkerStyle);
0904     marker.addAttribute("draw:display-name",  QString(markerStyles[id]).replace("_20_", " "));
0905 
0906     // sync with LO
0907     switch (id) {
0908     case msolineArrowStealthEnd:
0909         marker.addAttribute("svg:viewBox", "0 0 318 318");
0910         marker.addAttribute("svg:d", "m159 0 159 318-159-127-159 127z");
0911         break;
0912     case msolineArrowDiamondEnd:
0913         marker.addAttribute("svg:viewBox", "0 0 318 318");
0914         marker.addAttribute("svg:d", "m159 0 159 159-159 159-159-159z");
0915         break;
0916     case msolineArrowOvalEnd:
0917         marker.addAttribute("svg:viewBox", "0 0 318 318");
0918         marker.addAttribute("svg:d", "m318 0c0-87-72-159-159-159s-159 72-159 159 72 159 159 159 159-72 159-159z");
0919         break;
0920     case msolineArrowOpenEnd:
0921         marker.addAttribute("svg:viewBox", "0 0 477 477");
0922         marker.addAttribute("svg:d", "m239 0 238 434-72 43-166-305-167 305-72-43z");
0923         break;
0924     case msolineArrowEnd:
0925     default:
0926         marker.addAttribute("svg:viewBox", "0 0 318 318");
0927         marker.addAttribute("svg:d", "m159 0 159 318h-318z");
0928         break;
0929     }
0930     return mainStyles.insert(marker, name, KoGenStyles::DontAddNumberToName);
0931 }
0932 
0933 qreal Utils::defineMarkerWidth(const QString &markerWidth, const qreal lineWidth)
0934 {
0935     int c = 0;
0936 
0937     if (markerWidth == "lg") {
0938         c = 3;
0939     } else if (markerWidth == "med" || markerWidth.isEmpty()) {
0940         c = 2; //MSOOXML default = "med"
0941     } else if (markerWidth == "sm") {
0942         c = 1;
0943     }
0944     return ( lineWidth * c );
0945 }
0946 
0947 //-----------------------------------------
0948 // XmlWriteBuffer
0949 //-----------------------------------------
0950 
0951 Utils::XmlWriteBuffer::XmlWriteBuffer()
0952         : m_origWriter(0), m_newWriter(0)
0953 {
0954 }
0955 
0956 Utils::XmlWriteBuffer::~XmlWriteBuffer()
0957 {
0958     releaseWriterInternal();
0959 }
0960 
0961 KoXmlWriter* Utils::XmlWriteBuffer::setWriter(KoXmlWriter* writer)
0962 {
0963     Q_ASSERT(!m_origWriter && !m_newWriter);
0964     if (m_origWriter || m_newWriter) {
0965         return 0;
0966     }
0967     m_origWriter = writer; // remember
0968     m_newWriter = new KoXmlWriter(&m_buffer, m_origWriter->indentLevel() + 1);
0969     return m_newWriter;
0970 }
0971 
0972 KoXmlWriter* Utils::XmlWriteBuffer::releaseWriter()
0973 {
0974     Q_ASSERT(m_newWriter && m_origWriter);
0975     if (!m_newWriter || !m_origWriter) {
0976         return 0;
0977     }
0978     m_origWriter->addCompleteElement(&m_buffer);
0979     return releaseWriterInternal();
0980 }
0981 
0982 KoXmlWriter* Utils::XmlWriteBuffer::releaseWriter(QString& bkpXmlSnippet)
0983 {
0984     Q_ASSERT(m_newWriter && m_origWriter);
0985     if (!m_newWriter || !m_origWriter) {
0986         return 0;
0987     }
0988     bkpXmlSnippet = QString::fromUtf8(m_buffer.buffer(), m_buffer.buffer().size());
0989     return releaseWriterInternal();
0990 }
0991 
0992 KoXmlWriter* Utils::XmlWriteBuffer::releaseWriterInternal()
0993 {
0994     if (!m_newWriter || !m_origWriter) {
0995         return 0;
0996     }
0997     delete m_newWriter;
0998     m_newWriter = 0;
0999     KoXmlWriter* tmp = m_origWriter;
1000     m_origWriter = 0;
1001     return tmp;
1002 }
1003 
1004 void Utils::XmlWriteBuffer::clear()
1005 {
1006     delete m_newWriter;
1007     m_newWriter = 0;
1008     m_origWriter = 0;
1009 }
1010 
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 
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 
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 
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 
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
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
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)
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 
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 
1134 qreal twipToPt(qreal v)
1135 {
1136     return TWIP_TO_POINT(v);
1137 }
1138 
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 
1144 qreal twipToCm(qreal v)
1145 {
1146     return TWIP_TO_CM(v);
1147 }
1148 
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 
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 
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 
1182 Utils::ParagraphBulletProperties::ParagraphBulletProperties()
1183 {
1184     clear();
1185 }
1186 
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 
1208 bool Utils::ParagraphBulletProperties::isEmpty() const
1209 {
1210     if (m_type == ParagraphBulletProperties::DefaultType) {
1211         return true;
1212     }
1213     return false;
1214 }
1215 
1216 void Utils::ParagraphBulletProperties::setAlign(const QString& align)
1217 {
1218     m_align = align;
1219 }
1220 
1221 void Utils::ParagraphBulletProperties::setBulletChar(const QString& bulletChar)
1222 {
1223     m_bulletChar = bulletChar;
1224     m_type = ParagraphBulletProperties::BulletType;
1225 }
1226 
1227 void Utils::ParagraphBulletProperties::setStartValue(const QString& value)
1228 {
1229     m_startValue = value;
1230 }
1231 
1232 void Utils::ParagraphBulletProperties::setMargin(const qreal margin)
1233 {
1234     m_margin = QString("%1").arg(margin);
1235 }
1236 
1237 void Utils::ParagraphBulletProperties::setIndent(const qreal indent)
1238 {
1239     m_indent = QString("%1").arg(indent);
1240 }
1241 
1242 void Utils::ParagraphBulletProperties::setPrefix(const QString& prefixChar)
1243 {
1244     m_prefix = prefixChar;
1245 }
1246 
1247 void Utils::ParagraphBulletProperties::setSuffix(const QString& suffixChar)
1248 {
1249     m_suffix = suffixChar;
1250 }
1251 
1252 void Utils::ParagraphBulletProperties::setNumFormat(const QString& numFormat)
1253 {
1254     m_numFormat = numFormat;
1255     m_type = ParagraphBulletProperties::NumberType;
1256 }
1257 
1258 void Utils::ParagraphBulletProperties::setPicturePath(const QString& picturePath)
1259 {
1260     m_picturePath = picturePath;
1261     m_type = ParagraphBulletProperties::PictureType;
1262 }
1263 
1264 void Utils::ParagraphBulletProperties::setBulletRelativeSize(const int size)
1265 {
1266     m_bulletRelativeSize = QString("%1").arg(size);
1267 }
1268 
1269 void Utils::ParagraphBulletProperties::setBulletSizePt(const qreal size)
1270 {
1271     m_bulletSize = QString("%1").arg(size);
1272 }
1273 
1274 void Utils::ParagraphBulletProperties::setBulletFont(const QString& font)
1275 {
1276     m_bulletFont = font;
1277 }
1278 
1279 void Utils::ParagraphBulletProperties::setBulletColor(const QString& bulletColor)
1280 {
1281     m_bulletColor = bulletColor;
1282 }
1283 
1284 void Utils::ParagraphBulletProperties::setFollowingChar(const QString& followingChar)
1285 {
1286     m_followingChar = followingChar;
1287 }
1288 
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 
1320 void Utils::ParagraphBulletProperties::setStartOverride(const bool startOverride)
1321 {
1322     m_startOverride = startOverride;
1323 }
1324 
1325 QString Utils::ParagraphBulletProperties::startValue() const
1326 {
1327     return m_startValue;
1328 }
1329 
1330 QString Utils::ParagraphBulletProperties::bulletColor() const
1331 {
1332     return m_bulletColor;
1333 }
1334 
1335 QString Utils::ParagraphBulletProperties::bulletChar() const
1336 {
1337     return m_bulletChar;
1338 }
1339 
1340 QString Utils::ParagraphBulletProperties::bulletFont() const
1341 {
1342     return m_bulletFont;
1343 }
1344 
1345 QString Utils::ParagraphBulletProperties::margin() const
1346 {
1347     return m_margin;
1348 }
1349 
1350 QString Utils::ParagraphBulletProperties::indent() const
1351 {
1352     return m_indent;
1353 }
1354 
1355 QString Utils::ParagraphBulletProperties::bulletRelativeSize() const
1356 {
1357     return m_bulletRelativeSize;
1358 }
1359 
1360 QString Utils::ParagraphBulletProperties::bulletSizePt() const
1361 {
1362     return m_bulletSize;
1363 }
1364 
1365 QString Utils::ParagraphBulletProperties::followingChar() const
1366 {
1367     return m_followingChar;
1368 }
1369 
1370 KoGenStyle Utils::ParagraphBulletProperties::textStyle() const
1371 {
1372     return m_textStyle;
1373 }
1374 
1375 bool Utils::ParagraphBulletProperties::startOverride() const
1376 {
1377     return m_startOverride;
1378 }
1379 
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 
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