Warning, file /office/calligra/filters/libmsooxml/MsooXmlUtils.cpp was not indexed or was modified since last indexation (in which case cross-reference links may be missing, inaccurate or erroneous).
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