File indexing completed on 2024-04-28 04:41:56
0001 /* This file is part of the KDE project 0002 * Copyright (C) 2001-2007 by OpenMFG, LLC <info@openmfg.com> 0003 * Copyright (C) 2007-2010 by Adam Pigg <adam@piggz.co.uk> 0004 * Copyright (C) 2011-2015 Jarosław Staniek <staniek@kde.org> 0005 * 0006 * This library is free software; you can redistribute it and/or 0007 * modify it under the terms of the GNU Lesser General Public 0008 * License as published by the Free Software Foundation; either 0009 * version 2.1 of the License, or (at your option) any later version. 0010 * 0011 * This library is distributed in the hope that it will be useful, 0012 * but WITHOUT ANY WARRANTY; without even the implied warranty of 0013 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 0014 * Lesser General Public License for more details. 0015 * 0016 * You should have received a copy of the GNU Lesser General Public 0017 * License along with this library. If not, see <http://www.gnu.org/licenses/>. 0018 */ 0019 0020 #include "KReportDesign_p.h" 0021 #include "KReportElement.h" 0022 #include "KReportUtils.h" 0023 #include "KReportUtils_p.h" 0024 #include "KReportPluginManager.h" 0025 #include "KReportPluginInterface.h" 0026 #include "kreport_debug.h" 0027 0028 #include <QDebug> 0029 #include <QDomDocument> 0030 #include <QDomElement> 0031 #include <QSizeF> 0032 0033 KReportDesign::Private::Private(KReportDesign *design) 0034 : q(design) 0035 , showGrid(DEFAULT_SHOW_GRID) 0036 , snapToGrid(DEFAULT_SNAP_TO_GRID) 0037 , gridDivisions(DEFAULT_GRID_DIVISIONS) 0038 , pageUnit(DEFAULT_UNIT) 0039 , sections(static_cast<int>(KReportSection::Type::Detail)) 0040 { 0041 memset(static_cast<void*>(sections.data()), 0, sizeof(void*) * sections.length()); 0042 pageLayout.setUnits(QPageLayout::Point); // initializate because of https://bugreports.qt.io/browse/QTBUG-47551 0043 } 0044 0045 KReportDesign::Private::~Private() 0046 { 0047 qDeleteAll(sections); 0048 } 0049 0050 KReportDesignGlobal::KReportDesignGlobal() 0051 : defaultSectionHeight(CM_TO_POINT(2.0)) 0052 , defaultSectionBackgroundColor(Qt::white) 0053 { 0054 defaultPageLayout.setUnits(QPageLayout::Point); 0055 defaultPageLayout.setMargins(QMarginsF(DEFAULT_PAGE_MARGIN_PT, 0056 DEFAULT_PAGE_MARGIN_PT, 0057 DEFAULT_PAGE_MARGIN_PT, 0058 DEFAULT_PAGE_MARGIN_PT)); 0059 defaultPageLayout.setMode(QPageLayout::StandardMode); 0060 defaultPageLayout.setOrientation(DEFAULT_PAGE_ORIENTATION); 0061 } 0062 0063 KReportSection::Type KReportDesignGlobal::sectionType(const QString& typeName) 0064 { 0065 initSectionTypes(); 0066 return sectionTypesForName.value(typeName); // returns Invalid type for invalid name 0067 } 0068 0069 inline uint qHash(KReportSection::Type sectionType, uint seed = 0) 0070 { 0071 return qHash(static_cast<uint>(sectionType), seed); 0072 } 0073 0074 QString KReportDesignGlobal::sectionTypeName(KReportSection::Type sectionType) 0075 { 0076 initSectionTypes(); 0077 return sectionTypeNames.value(sectionType); 0078 } 0079 0080 void KReportDesignGlobal::initSectionTypes() 0081 { 0082 if (!sectionTypesForName.isEmpty()) { 0083 return; 0084 } 0085 for (const SectionTypeInfo *info = sectionTypes; info->name; ++info) { 0086 sectionTypesForName.insert(QString::fromLatin1(info->name), info->type); 0087 sectionTypeNames.insert(info->type, QString::fromLatin1(info->name)); 0088 } 0089 } 0090 0091 const KReportDesignGlobal::SectionTypeInfo KReportDesignGlobal::sectionTypes[] = { 0092 { KReportSection::Type::Invalid, "" }, 0093 { KReportSection::Type::PageHeaderAny, "header-page-any" }, 0094 { KReportSection::Type::PageHeaderEven, "header-page-even" }, 0095 { KReportSection::Type::PageHeaderOdd, "header-page-odd" }, 0096 { KReportSection::Type::PageHeaderFirst, "header-page-first" }, 0097 { KReportSection::Type::PageHeaderLast, "header-page-last" }, 0098 { KReportSection::Type::PageFooterAny, "footer-page-any" }, 0099 { KReportSection::Type::PageFooterEven, "footer-page-even" }, 0100 { KReportSection::Type::PageFooterOdd, "footer-page-odd" }, 0101 { KReportSection::Type::PageFooterFirst, "footer-page-first" }, 0102 { KReportSection::Type::PageFooterLast, "footer-page-last" }, 0103 { KReportSection::Type::ReportHeader, "header-report" }, 0104 { KReportSection::Type::ReportFooter, "footer-report" }, 0105 { KReportSection::Type::GroupHeader, "group-header" }, 0106 { KReportSection::Type::GroupFooter, "group-footer" }, 0107 { KReportSection::Type::Detail, "detail" }, 0108 { KReportSection::Type::Invalid, nullptr } 0109 }; 0110 0111 Q_GLOBAL_STATIC(KReportDesignGlobal, s_global) 0112 0113 //static 0114 KReportDesignGlobal* KReportDesignGlobal::self() 0115 { 0116 return s_global; 0117 } 0118 0119 static void setStatus(KReportDesignReadingStatus *status, const QString& details, 0120 const QDomNode &node) 0121 { 0122 if (status) { 0123 status->setErrorDetails(details); 0124 status->setErrorLineNumber(node.lineNumber() == -1 ? 0 /* mark error */ : node.lineNumber()); 0125 status->setErrorColumnNumber(node.columnNumber() == -1 ? 0 /* mark error */ : node.columnNumber()); 0126 } 0127 } 0128 0129 static bool checkElement(const QDomNode &node, KReportDesignReadingStatus *status) 0130 { 0131 if (node.isElement()) { 0132 return true; 0133 } 0134 setStatus(status, QString::fromLatin1("Element expected inside of <%1>") 0135 .arg(node.parentNode().toElement().tagName()), node); 0136 return false; 0137 } 0138 0139 static void setNoAttributeStatus(const QDomElement &el, const QString &attrName, KReportDesignReadingStatus *status) 0140 { 0141 setStatus(status, QString::fromLatin1("Attribute \"%1\" expected inside of <%1>") 0142 .arg(attrName).arg(el.tagName()), el); 0143 } 0144 0145 #if 0 // TODO unused for now 0146 static bool checkAttribute(const QDomElement &el, const char *attrName, KReportDesignReadingStatus *status) 0147 { 0148 if (el.hasAttribute(QLatin1String(attrName))) { 0149 return true; 0150 } 0151 setNoAttributeStatus(el, attrName, status); 0152 return false; 0153 } 0154 #endif 0155 0156 KReportSection KReportDesign::Private::processSectionElement(const QDomElement &el, 0157 KReportDesignReadingStatus *status) 0158 { 0159 kreportDebug() << el.nodeName(); 0160 const KReportSection::Type sectionType 0161 = s_global->sectionType(KReportUtils::readSectionTypeNameAttribute(el)); 0162 if (sectionType == KReportSection::Type::Invalid) { 0163 setStatus(status, 0164 QString::fromLatin1( 0165 "Invalid value of report:section-type=\"%1\" in element <%2>") 0166 .arg(KReportUtils::readSectionTypeNameAttribute(el)) 0167 .arg(el.tagName()), 0168 el); 0169 return KReportSection(); 0170 } 0171 KReportSection section; 0172 section.setType(sectionType); 0173 const QSizeF size(KReportUtils::readSizeAttributes(el)); 0174 if (size.height() >= 0) { 0175 section.setHeight(size.height()); 0176 } 0177 section.setBackgroundColor( 0178 KReportUtils::attr(el, QLatin1String("fo:background-color"), QColor())); 0179 for (QDomNode node = el.firstChild(); !node.isNull(); node = node.nextSibling()) { 0180 if (!checkElement(node, status)) { 0181 return KReportSection(); 0182 } 0183 KReportElement element = processSectionElementChild(node.toElement(), status); 0184 if (!element.rect().isValid() || (status && status->isError())) { 0185 return KReportSection(); 0186 } 0187 (void)section.addElement(element); 0188 } 0189 return section; 0190 } 0191 0192 KReportPluginInterface* KReportDesign::Private::findPlugin(const QString &typeName, 0193 const QDomElement &el, 0194 KReportDesignReadingStatus *status) 0195 { 0196 KReportPluginInterface* plugin = KReportPluginManager::self()->plugin(typeName); 0197 if (!plugin) { 0198 setStatus(status, QString::fromLatin1("No such plugin \"%1\"").arg(typeName), el); 0199 return nullptr; 0200 } 0201 return plugin; 0202 } 0203 0204 KReportElement KReportDesign::Private::processSectionElementChild(const QDomElement &el, 0205 KReportDesignReadingStatus *status) 0206 { 0207 const QByteArray name = el.tagName().toLatin1(); 0208 const char* elNamespace = "report:"; 0209 0210 if (!name.startsWith(elNamespace)) { 0211 unexpectedElement(el, status); 0212 return KReportElement(); 0213 } 0214 const QByteArray reportElementName = name.mid(qstrlen(elNamespace)); 0215 //qDebug() << "Found Report Element:" << reportElementName; 0216 KReportPluginInterface *plugin = findPlugin(QLatin1String(reportElementName), el, status); 0217 if (!plugin) { 0218 return KReportElement(); 0219 } 0220 KReportElement element = plugin->createElement(); 0221 if (!plugin->loadElement(&element, el, status)) { 0222 return KReportElement(); 0223 } 0224 element.setName(KReportUtils::readNameAttribute(el)); 0225 if (element.name().isEmpty()) { 0226 setNoAttributeStatus(el, QLatin1String("report:name"), status); 0227 return KReportElement(); 0228 } 0229 return element; 0230 } 0231 0232 bool KReportDesign::Private::processGroupElement(const QDomElement &el, 0233 KReportDesignReadingStatus *status) 0234 { 0235 Q_UNUSED(el); 0236 Q_UNUSED(status); 0237 //! @todo 0238 return true; 0239 } 0240 0241 //! The report:detail element contains a single report:section child of type 'detail' 0242 //! and 0 or more report:group children. 0243 bool KReportDesign::Private::processDetailElement(const QDomElement &el, 0244 KReportDesignReadingStatus *status) 0245 { 0246 QDomElement sectionEl; 0247 for (QDomNode node = el.firstChild(); !node.isNull(); node = node.nextSibling()) { 0248 if (!checkElement(node, status)) { 0249 return false; 0250 } 0251 QDomElement childEl = node.toElement(); 0252 const QByteArray name = childEl.tagName().toLatin1(); 0253 if (name == "report:section") { 0254 if (!sectionEl.isNull()) { 0255 return false; 0256 } 0257 KReportSection section = processSectionElement(childEl, status); 0258 if (status && status->isError()) { 0259 return false; 0260 } 0261 if (section.type() != KReportSection::Type::Detail) { 0262 setStatus(status, 0263 QString::fromLatin1("Only section of type \"detail\" allowed in <report:detail>"), el); 0264 return false; 0265 } 0266 q->addSection(section); 0267 } 0268 else if (name == "report:group") { 0269 if (!processGroupElement(childEl, status)) { 0270 return false; 0271 } 0272 } 0273 else { 0274 unexpectedElement(childEl, status); 0275 return false; 0276 } 0277 } 0278 // finally make sure we have one report:section 0279 (void)requiredChildElement(el, "report:section", status); 0280 if (status && status->isError()) { 0281 return false; 0282 } 0283 return true; 0284 } 0285 0286 /*! <pre> 0287 <report:body> 0288 *<report:section>..</report:section> (up to 12 sections) 0289 <report:detail> 0290 <report:group> // any number of groups 0291 *<report:section>..</report:section> (group-header, group-footer) 0292 </report:group> 0293 </report:detail> 0294 <report:body> 0295 <pre> 0296 */ 0297 bool KReportDesign::Private::processBodyElementChild(const QDomElement &el, 0298 KReportDesignReadingStatus *status) 0299 { 0300 const QByteArray name = el.tagName().toLatin1(); 0301 //kreportDebug() << name; 0302 if (name == "report:section") { 0303 KReportSection section = processSectionElement(el, status); 0304 if (status && status->isError()) { 0305 return false; 0306 } 0307 if (q->hasSection(section.type())) { 0308 setStatus(status, QString::fromLatin1("Could not add two sections of type \"%1\" " 0309 "to the same report design") 0310 .arg(s_global->sectionTypeName(section.type())), el); 0311 return false; 0312 } 0313 if (section.type() == KReportSection::Type::Detail) { 0314 setStatus(status, 0315 QString::fromLatin1("Section of type \"detail\" not allowed in <report:body>"), el); 0316 return false; 0317 } 0318 q->addSection(section); 0319 #if 0 //TODO 0320 if (section(KReportSectionData::sectionTypeFromString(sectiontype)) == 0) { 0321 insertSection(KReportSectionData::sectionTypeFromString(sectiontype)); 0322 section(KReportSectionData::sectionTypeFromString(sectiontype))->initFromXML(sec); 0323 } 0324 #endif 0325 } else if (name == "report:detail") { 0326 if (!processDetailElement(el, status)) { 0327 return false; 0328 } 0329 #if 0 //TODO 0330 ReportSectionDetail * rsd = new ReportSectionDetail(this); 0331 rsd->initFromXML(&sec); 0332 setDetail(rsd); 0333 #endif 0334 } 0335 return true; 0336 } 0337 0338 /* NOTE: don't translate these extremely detailed messages. */ 0339 //! @todo Load page options 0340 bool KReportDesign::Private::processContentElementChild(const QDomElement &el, 0341 KReportDesignReadingStatus *status) 0342 { 0343 const QByteArray name = el.tagName().toLatin1(); 0344 QPageLayout defaultPageLayout = KReportDesign::defaultPageLayout(); 0345 //kreportDebug() << name; 0346 if (name == "report:title") { 0347 title = el.text(); 0348 #ifdef KREPORT_SCRIPTING 0349 } else if (name == "report:script") { 0350 script = el.firstChildElement().text(); 0351 originalInterpreter = KReportUtils::attr( 0352 el, QLatin1String("report:script-interpreter"), QString()); 0353 #endif 0354 } else if (name == "report:grid") { 0355 showGrid = KReportUtils::attr( 0356 el, QLatin1String("report:grid-visible"), DEFAULT_SHOW_GRID); 0357 snapToGrid = KReportUtils::attr( 0358 el, QLatin1String("report:grid-snap"), DEFAULT_SNAP_TO_GRID); 0359 gridDivisions = KReportUtils::attr( 0360 el, QLatin1String("report:grid-divisions"), DEFAULT_GRID_DIVISIONS); 0361 const QString pageUnitString 0362 = KReportUtils::attr(el, QLatin1String("report:page-unit"), QString()); 0363 0364 pageUnit = KReportUnit::symbolToType(pageUnitString); 0365 if (!pageUnit.isValid()) { 0366 pageUnit = DEFAULT_UNIT; 0367 if (!pageUnitString.isEmpty()) { 0368 kreportWarning() << "Invalid page unit" << pageUnitString << "specified in" << name 0369 << "element, defaulting to" << pageUnit.symbol(); 0370 } 0371 } 0372 } 0373 else if (name == "report:page-style") { // see https://git.reviewboard.kde.org/r/115314 0374 const QByteArray pagetype = el.text().toLatin1(); 0375 if (pagetype == "predefined") { 0376 pageLayout.setPageSize(KReportUtils::pageSize( 0377 KReportUtils::attr(el, QLatin1String("report:page-size"), 0378 QPageSize(DEFAULT_PAGE_SIZE).key()))); 0379 } else if (pagetype.isEmpty() || pagetype == "custom") { 0380 QSizeF size(KReportUtils::attr(el, QLatin1String("fo:page-width"), -1.0), 0381 KReportUtils::attr(el, QLatin1String("fo:page-height"), -1.0)); 0382 if (size.isValid()) { 0383 pageLayout.setPageSize(QPageSize(size, QPageSize::Point)); 0384 } else { 0385 pageLayout.setPageSize(defaultPageLayout.pageSize()); 0386 } 0387 } else if (pagetype == "label") { 0388 //! @todo? 0389 pageLayout.setPageSize(defaultPageLayout.pageSize()); 0390 } 0391 QMarginsF margins(KReportUtils::attr(el, QLatin1String("fo:margin-left"), 0392 defaultPageLayout.margins().left()), 0393 KReportUtils::attr(el, QLatin1String("fo:margin-top"), 0394 defaultPageLayout.margins().top()), 0395 KReportUtils::attr(el, QLatin1String("fo:margin-right"), 0396 defaultPageLayout.margins().right()), 0397 KReportUtils::attr(el, QLatin1String("fo:margin-bottom"), 0398 defaultPageLayout.margins().bottom())); 0399 bool b = pageLayout.setMargins(margins); 0400 if (!b) { 0401 qWarning() << "Failed to set page margins to" << margins; 0402 } 0403 const QString s = KReportUtils::attr( 0404 el, QLatin1String("report:print-orientation"), QString()); 0405 if (s == QLatin1String("portrait")) { 0406 pageLayout.setOrientation(QPageLayout::Portrait); 0407 } else if (s == QLatin1String("landscape")) { 0408 pageLayout.setOrientation(QPageLayout::Landscape); 0409 } 0410 else { 0411 pageLayout.setOrientation(defaultPageLayout.orientation()); 0412 } 0413 } else if (name == "report:body") { 0414 for (QDomNode node = el.firstChild(); !node.isNull(); node = node.nextSibling()) { 0415 if (!checkElement(node, status)) { 0416 return false; 0417 } 0418 if (!processBodyElementChild(node.toElement(), status)) { 0419 return false; 0420 } 0421 } 0422 } 0423 return true; 0424 } 0425 0426 void KReportDesign::Private::unexpectedElement(const QDomElement &element, 0427 KReportDesignReadingStatus *status) const 0428 { 0429 setStatus(status, QString::fromLatin1("Unexpected child element <%1> found in <%2>") 0430 .arg(element.tagName()).arg(element.parentNode().toElement().tagName()), element); 0431 } 0432 0433 QDomElement KReportDesign::Private::requiredChildElement(const QDomElement &parent, 0434 const char* childElementName, 0435 KReportDesignReadingStatus *status) const 0436 { 0437 const QDomElement result = parent.firstChildElement(QLatin1String(childElementName)); 0438 if (result.isNull()) { 0439 setStatus(status, QString::fromLatin1("Child element <%1> not found in <%2>") 0440 .arg(QLatin1String(childElementName)).arg(parent.tagName()), parent); 0441 } 0442 return result; 0443 } 0444 0445 /* NOTE: don't translate these extremely detailed messages. */ 0446 bool KReportDesign::Private::processDocument(const QDomDocument &doc, 0447 KReportDesignReadingStatus *status) 0448 { 0449 const QDomElement rootEl = doc.documentElement(); 0450 const QLatin1String rootElName("kexireport"); // legacy name kept for compatibility 0451 if (doc.doctype().name() != rootElName) { 0452 setStatus(status, QString::fromLatin1("Document type should be \"%1\"").arg(rootElName), rootEl); 0453 return false; 0454 } 0455 if (rootEl.tagName() != rootElName) { 0456 setStatus(status, QString::fromLatin1("Root element should be <%1>").arg(rootElName), rootEl); 0457 return false; 0458 } 0459 const QDomElement contentEl = requiredChildElement(rootEl, "report:content", status); 0460 if (status && status->isError()) { 0461 return false; 0462 } 0463 //! @todo check namespaces as in: 0464 //! <report:content xmlns:fo="urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0" 0465 //! xmlns:report="http://kexi-project.org/report/2.0" 0466 //! xmlns:svg="urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0"> 0467 0468 // deleteDetail(); 0469 0470 for (QDomNode node = contentEl.firstChild(); !node.isNull(); node = node.nextSibling()) { 0471 if (!checkElement(node, status)) { 0472 return false; 0473 } 0474 if (!processContentElementChild(node.toElement(), status)) { 0475 return false; 0476 } 0477 } 0478 0479 if (status) { 0480 *status = KReportDesignReadingStatus(); 0481 } 0482 return true; 0483 }