From c82cf47e2fde6a7ac8382a5004c0b1762b4b3d1d Mon Sep 17 00:00:00 2001 From: Debao Zhang Date: Thu, 28 Nov 2013 00:39:23 +0800 Subject: [PATCH] Add basic conditional formatting writer support. --- .../conditionalformatting.pro | 9 + .../doc/src/conditionalformatting.qdoc | 10 + examples/xlsx/conditionalformatting/main.cpp | 56 +++ examples/xlsx/xlsx.pro | 1 + src/xlsx/qtxlsx.pri | 7 +- src/xlsx/xlsxconditionalformatting.cpp | 392 ++++++++++++++++++ src/xlsx/xlsxconditionalformatting.h | 119 ++++++ src/xlsx/xlsxconditionalformatting_p.h | 94 +++++ src/xlsx/xlsxdocument.cpp | 8 + src/xlsx/xlsxdocument.h | 2 + src/xlsx/xlsxformat.cpp | 24 ++ src/xlsx/xlsxformat.h | 8 +- src/xlsx/xlsxworksheet.cpp | 19 + src/xlsx/xlsxworksheet.h | 2 + src/xlsx/xlsxworksheet_p.h | 2 + tests/auto/auto.pro | 3 +- .../tst_conditionalformattingtest.cpp | 83 ++++ .../xlsxconditionalformatting.pro | 12 + 18 files changed, 844 insertions(+), 7 deletions(-) create mode 100644 examples/xlsx/conditionalformatting/conditionalformatting.pro create mode 100644 examples/xlsx/conditionalformatting/doc/src/conditionalformatting.qdoc create mode 100644 examples/xlsx/conditionalformatting/main.cpp create mode 100644 src/xlsx/xlsxconditionalformatting.cpp create mode 100644 src/xlsx/xlsxconditionalformatting.h create mode 100644 src/xlsx/xlsxconditionalformatting_p.h create mode 100644 tests/auto/xlsxconditionalformatting/tst_conditionalformattingtest.cpp create mode 100644 tests/auto/xlsxconditionalformatting/xlsxconditionalformatting.pro diff --git a/examples/xlsx/conditionalformatting/conditionalformatting.pro b/examples/xlsx/conditionalformatting/conditionalformatting.pro new file mode 100644 index 0000000..10d2978 --- /dev/null +++ b/examples/xlsx/conditionalformatting/conditionalformatting.pro @@ -0,0 +1,9 @@ +TARGET = hello + +#include(../../../src/xlsx/qtxlsx.pri) +QT+=xlsx + +CONFIG += console +CONFIG -= app_bundle + +SOURCES += main.cpp diff --git a/examples/xlsx/conditionalformatting/doc/src/conditionalformatting.qdoc b/examples/xlsx/conditionalformatting/doc/src/conditionalformatting.qdoc new file mode 100644 index 0000000..39e2461 --- /dev/null +++ b/examples/xlsx/conditionalformatting/doc/src/conditionalformatting.qdoc @@ -0,0 +1,10 @@ +/*! + \example conditionalformatting + \title Conditional Formatting Example + \brief This is a conditional formatting example. + \ingroup qtxlsx-examples + + This example demonstrates how to create a new + .xlsx file containin conditional formatting + with Qt Xlsx Library. So lets see how this is achieved. +*/ diff --git a/examples/xlsx/conditionalformatting/main.cpp b/examples/xlsx/conditionalformatting/main.cpp new file mode 100644 index 0000000..59908bc --- /dev/null +++ b/examples/xlsx/conditionalformatting/main.cpp @@ -0,0 +1,56 @@ +#include +#include "xlsxdocument.h" +#include "xlsxconditionalformatting.h" + +using namespace QXlsx; + +int main() +{ + //![0] + Document xlsx; + Format hFmt; + hFmt.setFontBold(true); + xlsx.write("B1", "(-inf,40)", hFmt); + xlsx.write("D1", "[30,70]", hFmt); + + for (int row=3; row<22; ++row) { + for (int col=2; col<22; ++col) + xlsx.write(row, col, qrand() % 100); + } + //![0] + + //![1] + ConditionalFormatting cf1; + Format fmt1; + fmt1.setFontColor(Qt::green); + fmt1.setBorderStyle(Format::BorderDashed); + cf1.addHighlightCellsRule(ConditionalFormatting::Highlight_LessThan, "40", fmt1); + cf1.addRange("B3:B21"); + xlsx.addConditionalFormatting(cf1); + //![1] + + //![cf2] + ConditionalFormatting cf2; + Format fmt2; + fmt2.setBorderStyle(Format::BorderDotted); + fmt2.setBorderColor(Qt::blue); + cf2.addHighlightCellsRule(ConditionalFormatting::Highlight_Between, "30", "70", fmt2); + cf2.addRange("D3:D21"); + xlsx.addConditionalFormatting(cf2); + //![cf2] + + //![cf3] + ConditionalFormatting cf3; + Format fmt3; + fmt3.setFontStrikeOut(true); + cf3.addHighlightCellsRule(ConditionalFormatting::Highlight_BeginsWith, "2", fmt3); + cf3.addRange("F3:F21"); + xlsx.addConditionalFormatting(cf3); + //![cf3] + + //![2] + xlsx.save(); + //![2] + + return 0; +} diff --git a/examples/xlsx/xlsx.pro b/examples/xlsx/xlsx.pro index 36a30ec..e5a1e42 100644 --- a/examples/xlsx/xlsx.pro +++ b/examples/xlsx/xlsx.pro @@ -11,5 +11,6 @@ SUBDIRS = hello \ definename \ formulas \ richtext \ + conditionalformatting \ demo diff --git a/src/xlsx/qtxlsx.pri b/src/xlsx/qtxlsx.pri index 190c477..51aa53f 100755 --- a/src/xlsx/qtxlsx.pri +++ b/src/xlsx/qtxlsx.pri @@ -31,7 +31,9 @@ HEADERS += $$PWD/xlsxdocpropscore_p.h \ $$PWD/xlsxdatavalidation_p.h \ $$PWD/xlsxcellrange.h \ $$PWD/xlsxrichstring_p.h \ - $$PWD/xlsxrichstring.h + $$PWD/xlsxrichstring.h \ + $$PWD/xlsxconditionalformatting.h \ + $$PWD/xlsxconditionalformatting_p.h SOURCES += $$PWD/xlsxdocpropscore.cpp \ $$PWD/xlsxdocpropsapp.cpp \ @@ -52,4 +54,5 @@ SOURCES += $$PWD/xlsxdocpropscore.cpp \ $$PWD/xlsxcell.cpp \ $$PWD/xlsxdatavalidation.cpp \ $$PWD/xlsxcellrange.cpp \ - $$PWD/xlsxrichstring.cpp + $$PWD/xlsxrichstring.cpp \ + $$PWD/xlsxconditionalformatting.cpp diff --git a/src/xlsx/xlsxconditionalformatting.cpp b/src/xlsx/xlsxconditionalformatting.cpp new file mode 100644 index 0000000..ff35cd2 --- /dev/null +++ b/src/xlsx/xlsxconditionalformatting.cpp @@ -0,0 +1,392 @@ +/**************************************************************************** +** Copyright (c) 2013 Debao Zhang +** All right reserved. +** +** Permission is hereby granted, free of charge, to any person obtaining +** a copy of this software and associated documentation files (the +** "Software"), to deal in the Software without restriction, including +** without limitation the rights to use, copy, modify, merge, publish, +** distribute, sublicense, and/or sell copies of the Software, and to +** permit persons to whom the Software is furnished to do so, subject to +** the following conditions: +** +** The above copyright notice and this permission notice shall be +** included in all copies or substantial portions of the Software. +** +** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +** EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +** MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +** NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +** LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +** OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +** WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +** +****************************************************************************/ + +#include "xlsxconditionalformatting.h" +#include "xlsxconditionalformatting_p.h" +#include "xlsxworksheet.h" +#include "xlsxcellrange.h" + +#include +#include +#include + +QT_BEGIN_NAMESPACE_XLSX + +ConditionalFormattingPrivate::ConditionalFormattingPrivate() +{ + +} + +ConditionalFormattingPrivate::ConditionalFormattingPrivate(const ConditionalFormattingPrivate &other) + :QSharedData(other) +{ + +} + +ConditionalFormattingPrivate::~ConditionalFormattingPrivate() +{ + +} + +/*! + * \class ConditionalFormatting + * \brief Conditional formatting for single cell or ranges + * \inmodule QtXlsx + * + * The conditional formatting can be applied to a single cell or ranges of cells. + */ + + +/*! + \enum ConditionalFormatting::HighlightRuleType + + \value Highlight_LessThan + \value Highlight_LessThanOrEqual + \value Highlight_Equal + \value Highlight_NotEqual + \value Highlight_GreaterThanOrEqual + \value Highlight_GreaterThan + \value Highlight_Between + \value Highlight_NotBetween + + \value Highlight_ContainsText + \value Highlight_NotContainsText + \value Highlight_BeginsWith + \value Highlight_EndsWith + + \value Highlight_TimePeriod + + \value Highlight_Duplicate + \value Highlight_Unique + + \value Highlight_Blanks + \value Highlight_NoBlanks + \value Highlight_Errors + \value Highlight_NoErrors + + \value Highlight_Top + \value Highlight_TopPercent + \value Highlight_Bottom + \value Highlight_BottomPercent + + \value Highlight_AboveAverage + \value Highlight_AboveOrEqualAverage + \value Highlight_BelowAverage + \value Highlight_BelowOrEqualAverage + \value Highlight_AboveStdDev1 + \value Highlight_AboveStdDev2 + \value Highlight_AboveStdDev3 + \value Highlight_BelowStdDev1 + \value Highlight_BelowStdDev2 + \value Highlight_BelowStdDev3 + + \value Highlight_SatisfyFormula +*/ + +/*! + Construct a conditional formatting object +*/ +ConditionalFormatting::ConditionalFormatting() + :d(new ConditionalFormattingPrivate()) +{ + +} + +/*! + Constructs a copy of \a other. +*/ +ConditionalFormatting::ConditionalFormatting(const ConditionalFormatting &other) + :d(other.d) +{ + +} + +/*! + Assigns \a other to this conditional formatting and returns a reference to + this conditional formatting. + */ +ConditionalFormatting &ConditionalFormatting::operator=(const ConditionalFormatting &other) +{ + this->d = other.d; + return *this; +} + + +/*! + * Destroy the object. + */ +ConditionalFormatting::~ConditionalFormatting() +{ +} + +/*! + * Add a hightlight rule with the given \a type, \a formula1, \a formula2, + * \a format and \a stopIfTrue. + */ +bool ConditionalFormatting::addHighlightCellsRule(HighlightRuleType type, const QString &formula1, const QString &formula2, const Format &format, bool stopIfTrue) +{ + if (format.isEmpty()) + return false; + + bool skipFormula1 = false; + + QSharedPointer cfRule(new XlsxCfRuleData); + if (type >= Highlight_LessThan && type <= Highlight_NotBetween) { + cfRule->attrs[XlsxCfRuleData::A_type] = QStringLiteral("cellIs"); + QString op; + switch (type) { + case Highlight_Between: op = QStringLiteral("between"); break; + case Highlight_Equal: op = QStringLiteral("equal"); break; + case Highlight_GreaterThan: op = QStringLiteral("greaterThan"); break; + case Highlight_GreaterThanOrEqual: op = QStringLiteral("greaterThanOrEqual"); break; + case Highlight_LessThan: op = QStringLiteral("lessThan"); break; + case Highlight_LessThanOrEqual: op = QStringLiteral("lessThanOrEqual"); break; + case Highlight_NotBetween: op = QStringLiteral("notBetween"); break; + case Highlight_NotEqual: op = QStringLiteral("notEqual"); break; + default: break; + } + cfRule->attrs[XlsxCfRuleData::A_operator] = op; + } else if (type >= Highlight_ContainsText && type <= Highlight_EndsWith) { + if (type == Highlight_ContainsText) { + cfRule->attrs[XlsxCfRuleData::A_type] = QStringLiteral("containsText"); + cfRule->attrs[XlsxCfRuleData::A_operator] = QStringLiteral("containsText"); + cfRule->attrs[XlsxCfRuleData::A_formula1_temp] = QStringLiteral("NOT(ISERROR(SEARCH(\"%1\",%2)))").arg(formula1); + } else if (type == Highlight_NotContainsText) { + cfRule->attrs[XlsxCfRuleData::A_type] = QStringLiteral("notContainsText"); + cfRule->attrs[XlsxCfRuleData::A_operator] = QStringLiteral("notContains"); + cfRule->attrs[XlsxCfRuleData::A_formula1_temp] = QStringLiteral("ISERROR(SEARCH(\"%2\",%1))").arg(formula1); + } else if (type == Highlight_BeginsWith) { + cfRule->attrs[XlsxCfRuleData::A_type] = QStringLiteral("beginsWith"); + cfRule->attrs[XlsxCfRuleData::A_operator] = QStringLiteral("beginsWith"); + cfRule->attrs[XlsxCfRuleData::A_formula1_temp] = QStringLiteral("LEFT(%2,LEN(\"%1\"))=\"%1\"").arg(formula1); + } else { + cfRule->attrs[XlsxCfRuleData::A_type] = QStringLiteral("endsWith"); + cfRule->attrs[XlsxCfRuleData::A_operator] = QStringLiteral("endsWith"); + cfRule->attrs[XlsxCfRuleData::A_formula1_temp] = QStringLiteral("RIGHT(%2,LEN(\"%1\"))=\"%1\"").arg(formula1); + } + cfRule->attrs[XlsxCfRuleData::A_text] = formula1; + skipFormula1 = true; + } else if (type == Highlight_TimePeriod) { + cfRule->attrs[XlsxCfRuleData::A_type] = QStringLiteral("timePeriod"); + //:Todo + return false; + } else if (type == Highlight_Duplicate) { + cfRule->attrs[XlsxCfRuleData::A_type] = QStringLiteral("duplicateValues"); + } else if (type == Highlight_Unique) { + cfRule->attrs[XlsxCfRuleData::A_type] = QStringLiteral("uniqueValues"); + } else if (type == Highlight_Errors) { + cfRule->attrs[XlsxCfRuleData::A_type] = QStringLiteral("containsErrors"); + cfRule->attrs[XlsxCfRuleData::A_formula1_temp] = QStringLiteral("ISERROR(%1)"); + skipFormula1 = true; + } else if (type == Highlight_NoErrors) { + cfRule->attrs[XlsxCfRuleData::A_type] = QStringLiteral("notContainsErrors"); + cfRule->attrs[XlsxCfRuleData::A_formula1_temp] = QStringLiteral("NOT(ISERROR(%1))"); + skipFormula1 = true; + } else if (type == Highlight_Blanks) { + cfRule->attrs[XlsxCfRuleData::A_type] = QStringLiteral("containsBlanks"); + cfRule->attrs[XlsxCfRuleData::A_formula1_temp] = QStringLiteral("LEN(TRIM(%1))=0"); + skipFormula1 = true; + } else if (type == Highlight_NoBlanks) { + cfRule->attrs[XlsxCfRuleData::A_type] = QStringLiteral("notContainsBlanks"); + cfRule->attrs[XlsxCfRuleData::A_formula1_temp] = QStringLiteral("LEN(TRIM(%1))>0"); + skipFormula1 = true; + } else if (type >= Highlight_Top && type <= Highlight_BottomPercent) { + cfRule->attrs[XlsxCfRuleData::A_type] = QStringLiteral("top10"); + if (type == Highlight_Bottom || type == Highlight_BottomPercent) + cfRule->attrs[XlsxCfRuleData::A_bottom] = QStringLiteral("1"); + if (type == Highlight_TopPercent || type == Highlight_BottomPercent) + cfRule->attrs[XlsxCfRuleData::A_percent] = QStringLiteral("1"); + cfRule->attrs[XlsxCfRuleData::A_rank] = !formula1.isEmpty() ? formula1 : QStringLiteral("10"); + skipFormula1 = true; + } else if (type >= Highlight_AboveAverage && type <= Highlight_BelowStdDev3) { + cfRule->attrs[XlsxCfRuleData::A_type] = QStringLiteral("aboveAverage"); + if (type >= Highlight_BelowAverage && type <= Highlight_BelowStdDev3) + cfRule->attrs[XlsxCfRuleData::A_aboveAverage] = QStringLiteral("0"); + if (type == Highlight_AboveOrEqualAverage || type == Highlight_BelowOrEqualAverage) + cfRule->attrs[XlsxCfRuleData::A_equalAverage] = QStringLiteral("1"); + if (type == Highlight_AboveStdDev1 || type == Highlight_BelowStdDev1) + cfRule->attrs[XlsxCfRuleData::A_stdDev] = QStringLiteral("1"); + else if (type == Highlight_AboveStdDev2 || type == Highlight_BelowStdDev2) + cfRule->attrs[XlsxCfRuleData::A_stdDev] = QStringLiteral("2"); + else if (type == Highlight_AboveStdDev3 || type == Highlight_BelowStdDev3) + cfRule->attrs[XlsxCfRuleData::A_stdDev] = QStringLiteral("3"); + } else { + return false; + } + + cfRule->dxfFormat = format; + if (stopIfTrue) + cfRule->attrs[XlsxCfRuleData::A_stopIfTrue] = true; + if (!formula1.isEmpty() && !skipFormula1) + cfRule->attrs[XlsxCfRuleData::A_formula1] = formula1.startsWith(QLatin1String("=")) ? formula1.mid(1) : formula1; + if (!formula2.isEmpty()) + cfRule->attrs[XlsxCfRuleData::A_formula2] = formula2.startsWith(QLatin1String("=")) ? formula2.mid(1) : formula2; + + d->cfRules.append(cfRule); + return true; +} + +/*! + * \overload + * + * Add a hightlight rule with the given \a type \a format and \a stopIfTrue. + */ +bool ConditionalFormatting::addHighlightCellsRule(HighlightRuleType type, const Format &format, bool stopIfTrue) +{ + if ((type >= Highlight_AboveAverage && type <= Highlight_BelowStdDev3) + || (type >= Highlight_Duplicate && type <= Highlight_NoErrors)) { + return addHighlightCellsRule(type, QString(), QString(), format, stopIfTrue); + } + + return false; +} + +/*! + * \overload + * + * Add a hightlight rule with the given \a type, \a formula, \a format and \a stopIfTrue. + */ +bool ConditionalFormatting::addHighlightCellsRule(HighlightRuleType type, const QString &formula, const Format &format, bool stopIfTrue) +{ + if (type == Highlight_Between || type == Highlight_NotBetween) + return false; + + return addHighlightCellsRule(type, formula, QString(), format, stopIfTrue); +} + +/*! + Returns the ranges on which the validation will be applied. + */ +QList ConditionalFormatting::ranges() const +{ + return d->ranges; +} + +/*! + Add the \a cell on which the conditional formatting will apply to. + */ +void ConditionalFormatting::addCell(const QString &cell) +{ + d->ranges.append(CellRange(cell)); +} + +/*! + \overload + Add the cell(\a row, \a col) on which the conditional formatting will apply to. + */ +void ConditionalFormatting::addCell(int row, int col) +{ + d->ranges.append(CellRange(row, col, row, col)); +} + +/*! + Add the \a range on which the conditional formatting will apply to. + */ +void ConditionalFormatting::addRange(const QString &range) +{ + d->ranges.append(CellRange(range)); +} + +/*! + \overload + Add the range(\a firstRow, \a firstCol, \a lastRow, \a lastCol) on + which the conditional formatting will apply to. + */ +void ConditionalFormatting::addRange(int firstRow, int firstCol, int lastRow, int lastCol) +{ + d->ranges.append(CellRange(firstRow, firstCol, lastRow, lastCol)); +} + +/*! + \overload + Add the \a range on which the conditional formatting will apply to. + */ +void ConditionalFormatting::addRange(const CellRange &range) +{ + d->ranges.append(range); +} + +bool ConditionalFormatting::loadFromXml(QXmlStreamReader &reader) const +{ + Q_ASSERT(reader.name() == QStringLiteral("conditionalFormatting")); + + return false; +} + +bool ConditionalFormatting::saveToXml(QXmlStreamWriter &writer) const +{ + writer.writeStartElement(QStringLiteral("conditionalFormatting")); + QStringList sqref; + foreach (CellRange range, ranges()) + sqref.append(range.toString()); + writer.writeAttribute(QStringLiteral("sqref"), sqref.join(QLatin1Char(' '))); + + for (int i=0; icfRules.size(); ++i) { + const QSharedPointer &rule = d->cfRules[i]; + writer.writeStartElement(QStringLiteral("cfRule")); + writer.writeAttribute(QStringLiteral("type"), rule->attrs[XlsxCfRuleData::A_type].toString()); + if (rule->dxfFormat.dxfIndexValid()) + writer.writeAttribute(QStringLiteral("dxfId"), QString::number(rule->dxfFormat.dxfIndex())); + writer.writeAttribute(QStringLiteral("priority"), QString::number(rule->priority)); + if (rule->attrs.contains(XlsxCfRuleData::A_stopIfTrue)) + writer.writeAttribute(QStringLiteral("stopIfTrue"), rule->attrs[XlsxCfRuleData::A_stopIfTrue].toString()); + if (rule->attrs.contains(XlsxCfRuleData::A_aboveAverage)) + writer.writeAttribute(QStringLiteral("aboveAverage"), rule->attrs[XlsxCfRuleData::A_aboveAverage].toString()); + if (rule->attrs.contains(XlsxCfRuleData::A_percent)) + writer.writeAttribute(QStringLiteral("percent"), rule->attrs[XlsxCfRuleData::A_percent].toString()); + if (rule->attrs.contains(XlsxCfRuleData::A_bottom)) + writer.writeAttribute(QStringLiteral("bottom"), rule->attrs[XlsxCfRuleData::A_bottom].toString()); + if (rule->attrs.contains(XlsxCfRuleData::A_operator)) + writer.writeAttribute(QStringLiteral("operator"), rule->attrs[XlsxCfRuleData::A_operator].toString()); + if (rule->attrs.contains(XlsxCfRuleData::A_text)) + writer.writeAttribute(QStringLiteral("text"), rule->attrs[XlsxCfRuleData::A_text].toString()); + if (rule->attrs.contains(XlsxCfRuleData::A_timePeriod)) + writer.writeAttribute(QStringLiteral("timePeriod"), rule->attrs[XlsxCfRuleData::A_timePeriod].toString()); + if (rule->attrs.contains(XlsxCfRuleData::A_rank)) + writer.writeAttribute(QStringLiteral("rank"), rule->attrs[XlsxCfRuleData::A_rank].toString()); + if (rule->attrs.contains(XlsxCfRuleData::A_stdDev)) + writer.writeAttribute(QStringLiteral("stdDev"), rule->attrs[XlsxCfRuleData::A_stdDev].toString()); + if (rule->attrs.contains(XlsxCfRuleData::A_equalAverage)) + writer.writeAttribute(QStringLiteral("equalAverage"), rule->attrs[XlsxCfRuleData::A_equalAverage].toString()); + + if (rule->attrs.contains(XlsxCfRuleData::A_formula1_temp)) { + QString startCell = ranges()[0].toString().split(QLatin1Char(':'))[0]; + writer.writeTextElement(QStringLiteral("formula"), rule->attrs[XlsxCfRuleData::A_formula1_temp].toString().arg(startCell)); + } else if (rule->attrs.contains(XlsxCfRuleData::A_formula1)) { + writer.writeTextElement(QStringLiteral("formula"), rule->attrs[XlsxCfRuleData::A_formula1].toString()); + } + if (rule->attrs.contains(XlsxCfRuleData::A_formula2)) + writer.writeTextElement(QStringLiteral("formula"), rule->attrs[XlsxCfRuleData::A_formula2].toString()); + if (rule->attrs.contains(XlsxCfRuleData::A_formula3)) + writer.writeTextElement(QStringLiteral("formula"), rule->attrs[XlsxCfRuleData::A_formula3].toString()); + + writer.writeEndElement(); //cfRule + } + + writer.writeEndElement(); //conditionalFormatting + return true; +} + +QT_END_NAMESPACE_XLSX diff --git a/src/xlsx/xlsxconditionalformatting.h b/src/xlsx/xlsxconditionalformatting.h new file mode 100644 index 0000000..9857ea5 --- /dev/null +++ b/src/xlsx/xlsxconditionalformatting.h @@ -0,0 +1,119 @@ +/**************************************************************************** +** Copyright (c) 2013 Debao Zhang +** All right reserved. +** +** Permission is hereby granted, free of charge, to any person obtaining +** a copy of this software and associated documentation files (the +** "Software"), to deal in the Software without restriction, including +** without limitation the rights to use, copy, modify, merge, publish, +** distribute, sublicense, and/or sell copies of the Software, and to +** permit persons to whom the Software is furnished to do so, subject to +** the following conditions: +** +** The above copyright notice and this permission notice shall be +** included in all copies or substantial portions of the Software. +** +** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +** EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +** MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +** NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +** LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +** OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +** WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +** +****************************************************************************/ +#ifndef QXLSX_XLSXCONDITIONALFORMATTING_H +#define QXLSX_XLSXCONDITIONALFORMATTING_H + +#include "xlsxglobal.h" +#include +#include +#include + +class QXmlStreamReader; +class QXmlStreamWriter; +class ConditionalFormattingTest; + +QT_BEGIN_NAMESPACE_XLSX + +class CellRange; +class Format; +class Worksheet; + +class ConditionalFormattingPrivate; +class Q_XLSX_EXPORT ConditionalFormatting +{ +public: + enum HighlightRuleType { + Highlight_LessThan, + Highlight_LessThanOrEqual, + Highlight_Equal, + Highlight_NotEqual, + Highlight_GreaterThanOrEqual, + Highlight_GreaterThan, + Highlight_Between, + Highlight_NotBetween, + + Highlight_ContainsText, + Highlight_NotContainsText, + Highlight_BeginsWith, + Highlight_EndsWith, + + Highlight_TimePeriod, + + Highlight_Duplicate, + Highlight_Unique, + Highlight_Blanks, + Highlight_NoBlanks, + Highlight_Errors, + Highlight_NoErrors, + + Highlight_Top, + Highlight_TopPercent, + Highlight_Bottom, + Highlight_BottomPercent, + + Highlight_AboveAverage, + Highlight_AboveOrEqualAverage, + Highlight_AboveStdDev1, + Highlight_AboveStdDev2, + Highlight_AboveStdDev3, + Highlight_BelowAverage, + Highlight_BelowOrEqualAverage, + Highlight_BelowStdDev1, + Highlight_BelowStdDev2, + Highlight_BelowStdDev3, + + Highlight_Expression + }; + + ConditionalFormatting(); + ConditionalFormatting(const ConditionalFormatting &other); + ~ConditionalFormatting(); + + bool addHighlightCellsRule(HighlightRuleType type, const Format &format, bool stopIfTrue=false); + bool addHighlightCellsRule(HighlightRuleType type, const QString &formula1, const Format &format, bool stopIfTrue=false); + bool addHighlightCellsRule(HighlightRuleType type, const QString &formula1, const QString &formula2, const Format &format, bool stopIfTrue=false); + + QList ranges() const; + + void addCell(const QString &cell); + void addCell(int row, int col); + void addRange(const QString &range); + void addRange(int firstRow, int firstCol, int lastRow, int lastCol); + void addRange(const CellRange &range); + + //needed by QSharedDataPointer!! + ConditionalFormatting &operator=(const ConditionalFormatting &other); + +private: + friend class Worksheet; + friend class ::ConditionalFormattingTest; + bool saveToXml(QXmlStreamWriter &writer) const; + bool loadFromXml(QXmlStreamReader &reader) const; + QSharedDataPointer d; +}; + +QT_END_NAMESPACE_XLSX + +#endif // QXLSX_XLSXCONDITIONALFORMATTING_H diff --git a/src/xlsx/xlsxconditionalformatting_p.h b/src/xlsx/xlsxconditionalformatting_p.h new file mode 100644 index 0000000..495de2c --- /dev/null +++ b/src/xlsx/xlsxconditionalformatting_p.h @@ -0,0 +1,94 @@ +/**************************************************************************** +** Copyright (c) 2013 Debao Zhang +** All right reserved. +** +** Permission is hereby granted, free of charge, to any person obtaining +** a copy of this software and associated documentation files (the +** "Software"), to deal in the Software without restriction, including +** without limitation the rights to use, copy, modify, merge, publish, +** distribute, sublicense, and/or sell copies of the Software, and to +** permit persons to whom the Software is furnished to do so, subject to +** the following conditions: +** +** The above copyright notice and this permission notice shall be +** included in all copies or substantial portions of the Software. +** +** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +** EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +** MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +** NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +** LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +** OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +** WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +** +****************************************************************************/ + +#ifndef XLSXCONDITIONALFORMATTING_P_H +#define XLSXCONDITIONALFORMATTING_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt Xlsx API. It exists for the convenience +// of the Qt Xlsx. This header file may change from +// version to version without notice, or even be removed. +// +// We mean it. +// + +#include "xlsxConditionalFormatting.h" +#include "xlsxformat.h" +#include +#include +#include + +QT_BEGIN_NAMESPACE_XLSX + +class XlsxCfRuleData +{ +public: + enum Attribute { + A_type, + A_dxfId, + //A_priority, + A_stopIfTrue, + A_aboveAverage, + A_percent, + A_bottom, + A_operator, + A_text, + A_timePeriod, + A_rank, + A_stdDev, + A_equalAverage, + + A_dxfFormat, + A_formula1, + A_formula2, + A_formula3, + A_formula1_temp + }; + + XlsxCfRuleData() + :priority(1) + {} + + int priority; + Format dxfFormat; + QMap attrs; +}; + +class ConditionalFormattingPrivate : public QSharedData +{ +public: + ConditionalFormattingPrivate(); + ConditionalFormattingPrivate(const ConditionalFormattingPrivate &other); + ~ConditionalFormattingPrivate(); + + QList >cfRules; + QList ranges; +}; + +QT_END_NAMESPACE_XLSX +#endif // XLSXCONDITIONALFORMATTING_P_H diff --git a/src/xlsx/xlsxdocument.cpp b/src/xlsx/xlsxdocument.cpp index 3d3a40d..dff9953 100644 --- a/src/xlsx/xlsxdocument.cpp +++ b/src/xlsx/xlsxdocument.cpp @@ -249,6 +249,14 @@ bool Document::addDataValidation(const DataValidation &validation) return currentWorksheet()->addDataValidation(validation); } +/*! + * Add a conditional formatting \a cf for current worksheet. Returns true if successful. + */ +bool Document::addConditionalFormatting(const ConditionalFormatting &cf) +{ + return currentWorksheet()->addConditionalFormatting(cf); +} + /*! * Returns a Cell object based on the given \a pos. 0 will be returned if the cell doesn't exist. */ diff --git a/src/xlsx/xlsxdocument.h b/src/xlsx/xlsxdocument.h index d8c369e..892f6a7 100644 --- a/src/xlsx/xlsxdocument.h +++ b/src/xlsx/xlsxdocument.h @@ -41,6 +41,7 @@ class Package; class Cell; class CellRange; class DataValidation; +class ConditionalFormatting; class DocumentPrivate; class Q_XLSX_EXPORT Document : public QObject @@ -69,6 +70,7 @@ public: bool groupRows(int rowFirst, int rowLast, bool collapsed = true); bool groupColumns(int colFirst, int colLast, bool collapsed = true); bool addDataValidation(const DataValidation &validation); + bool addConditionalFormatting(const ConditionalFormatting &cf); Cell *cellAt(const QString &cell) const; Cell *cellAt(int row, int col) const; diff --git a/src/xlsx/xlsxformat.cpp b/src/xlsx/xlsxformat.cpp index d9ba17c..e235fa2 100755 --- a/src/xlsx/xlsxformat.cpp +++ b/src/xlsx/xlsxformat.cpp @@ -891,6 +891,9 @@ int Format::borderIndex() const return d->border_index; } +/*! + * \internal + */ void Format::setBorderIndex(int index) { d->border_index = index; @@ -986,6 +989,9 @@ void Format::setPatternBackgroundColor(const QColor &color) setProperty(FormatPrivate::P_Fill_BgColor, color); } +/*! + * \internal + */ bool Format::fillIndexValid() const { if (!hasFillData()) @@ -993,6 +999,9 @@ bool Format::fillIndexValid() const return d->fill_index_valid; } +/*! + * \internal + */ int Format::fillIndex() const { if (!d) @@ -1000,6 +1009,9 @@ int Format::fillIndex() const return d->fill_index; } +/*! + * \internal + */ void Format::setFillIndex(int index) { d->fill_index = index; @@ -1133,8 +1145,14 @@ QByteArray Format::formatKey() const return d->formatKey; } +/*! + * \internal + * Called by QXlsx::Styles or some unittests. + */ void Format::setXfIndex(int index) { + if (!d) + d = new FormatPrivate; d->xf_index = index; d->xf_indexValid = true; } @@ -1159,8 +1177,14 @@ bool Format::xfIndexValid() const return d->xf_indexValid; } +/*! + * \internal + * Called by QXlsx::Styles or some unittests. + */ void Format::setDxfIndex(int index) { + if (!d) + d = new FormatPrivate; d->dxf_index = index; d->dxf_indexValid = true; } diff --git a/src/xlsx/xlsxformat.h b/src/xlsx/xlsxformat.h index cafa87b..d184f6e 100755 --- a/src/xlsx/xlsxformat.h +++ b/src/xlsx/xlsxformat.h @@ -252,10 +252,6 @@ public: int xfIndex() const; bool dxfIndexValid() const; int dxfIndex() const; -private: - friend class Styles; - friend class ::FormatTest; - friend Q_XLSX_EXPORT QDebug operator<<(QDebug, const Format &f); void fixNumberFormat(int id, const QString &format); void setFontIndex(int index); @@ -263,6 +259,10 @@ private: void setFillIndex(int index); void setXfIndex(int index); void setDxfIndex(int index); +private: + friend class Styles; + friend class ::FormatTest; + friend Q_XLSX_EXPORT QDebug operator<<(QDebug, const Format &f); int theme() const; diff --git a/src/xlsx/xlsxworksheet.cpp b/src/xlsx/xlsxworksheet.cpp index f724913..cfd9421 100755 --- a/src/xlsx/xlsxworksheet.cpp +++ b/src/xlsx/xlsxworksheet.cpp @@ -35,6 +35,7 @@ #include "xlsxcell.h" #include "xlsxcell_p.h" #include "xlsxcellrange.h" +#include "xlsxconditionalformatting_p.h" #include #include @@ -931,6 +932,22 @@ bool Worksheet::addDataValidation(const DataValidation &validation) return true; } +bool Worksheet::addConditionalFormatting(const ConditionalFormatting &cf) +{ + Q_D(Worksheet); + if (cf.ranges().isEmpty()) + return false; + + for (int i=0; icfRules.size(); ++i) { + const QSharedPointer &rule = cf.d->cfRules[i]; + if (!rule->dxfFormat.isEmpty()) + d->workbook->styles()->addDxfFormat(rule->dxfFormat); + rule->priority = 1; + } + d->conditionalFormattingList.append(cf); + return true; +} + int Worksheet::insertImage(int row, int column, const QImage &image, const QPointF &offset, double xScale, double yScale) { Q_D(Worksheet); @@ -1120,6 +1137,8 @@ void Worksheet::saveToXmlFile(QIODevice *device) writer.writeEndElement();//sheetData d->writeMergeCells(writer); + foreach (const ConditionalFormatting cf, d->conditionalFormattingList) + cf.saveToXml(writer); d->writeDataValidation(writer); d->writeHyperlinks(writer); d->writeDrawings(writer); diff --git a/src/xlsx/xlsxworksheet.h b/src/xlsx/xlsxworksheet.h index b16d08e..f38c04a 100755 --- a/src/xlsx/xlsxworksheet.h +++ b/src/xlsx/xlsxworksheet.h @@ -45,6 +45,7 @@ class Workbook; class Format; class Drawing; class DataValidation; +class ConditionalFormatting; class CellRange; struct XlsxImageData; class RichString; @@ -80,6 +81,7 @@ public: int writeHyperlink(int row, int column, const QUrl &url, const Format &format=Format(), const QString &display=QString(), const QString &tip=QString()); bool addDataValidation(const DataValidation &validation); + bool addConditionalFormatting(const ConditionalFormatting &cf); Cell *cellAt(const QString &row_column) const; Cell *cellAt(int row, int column) const; diff --git a/src/xlsx/xlsxworksheet_p.h b/src/xlsx/xlsxworksheet_p.h index f88be6e..972d1fc 100644 --- a/src/xlsx/xlsxworksheet_p.h +++ b/src/xlsx/xlsxworksheet_p.h @@ -40,6 +40,7 @@ #include "xlsxworksheet.h" #include "xlsxcell.h" #include "xlsxdatavalidation.h" +#include "xlsxconditionalformatting.h" #include #include @@ -215,6 +216,7 @@ public: QList > drawingLinks; QList dataValidationsList; + QList conditionalFormattingList; int xls_rowmax; int xls_colmax; diff --git a/tests/auto/auto.pro b/tests/auto/auto.pro index 3e10623..a9b56ec 100644 --- a/tests/auto/auto.pro +++ b/tests/auto/auto.pro @@ -10,4 +10,5 @@ SUBDIRS=\ sharedstrings \ styles \ format \ - richstring + richstring \ + xlsxconditionalformatting diff --git a/tests/auto/xlsxconditionalformatting/tst_conditionalformattingtest.cpp b/tests/auto/xlsxconditionalformatting/tst_conditionalformattingtest.cpp new file mode 100644 index 0000000..7781fb4 --- /dev/null +++ b/tests/auto/xlsxconditionalformatting/tst_conditionalformattingtest.cpp @@ -0,0 +1,83 @@ +#include "xlsxconditionalformatting.h" +#include "xlsxformat.h" +#include "private/xlsxconditionalformatting_p.h" + +#include +#include +#include +#include + +using namespace QXlsx; + +class ConditionalFormattingTest : public QObject +{ + Q_OBJECT + +public: + ConditionalFormattingTest(); + +private Q_SLOTS: + void testHighlightRules(); + void testHighlightRules_data(); +}; + +ConditionalFormattingTest::ConditionalFormattingTest() +{ +} + +void ConditionalFormattingTest::testHighlightRules_data() +{ + QTest::addColumn("type"); + QTest::addColumn("formula1"); + QTest::addColumn("formula2"); + QTest::addColumn("result"); + + QTest::newRow("lessThan")<<(int)ConditionalFormatting::Highlight_LessThan + <<"100" + <100"); + QTest::newRow("between")<<(int)ConditionalFormatting::Highlight_Between + <<"4" + <<"20" + <420"); + + QTest::newRow("containsText")<<(int)ConditionalFormatting::Highlight_ContainsText + <<"Qt" + <"); + QTest::newRow("beginsWith")<<(int)ConditionalFormatting::Highlight_BeginsWith + <<"Qt" + <LEFT(C3,LEN"); //(\"Qt\"))=\"Qt\""); + QTest::newRow("duplicateValues")<<(int)ConditionalFormatting::Highlight_Duplicate + <"); +} + +void ConditionalFormattingTest::testHighlightRules() +{ + QFETCH(int, type); + QFETCH(QString, formula1); + QFETCH(QString, formula2); + QFETCH(QByteArray, result); + + Format fmt; + fmt.setFontBold(true); + fmt.setDxfIndex(0); + + ConditionalFormatting cf; + cf.addHighlightCellsRule((ConditionalFormatting::HighlightRuleType)type, formula1, formula2, fmt); + cf.addRange("C3:C10"); + + QBuffer buffer; + buffer.open(QIODevice::WriteOnly); + QXmlStreamWriter writer(&buffer); + cf.saveToXml(writer); + qDebug()<