summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTor Lillqvist <tml@collabora.com>2017-11-26 23:28:05 +0200
committerTor Lillqvist <tml@collabora.com>2017-11-30 06:34:59 +0100
commitea55492a6e55290d92a59324b3cb31ed958981ab (patch)
tree58263fb7ad37f07f93739bfac67ea25ae674127e
parent42dafb5c7bd218f4d368fbd1113fa4a0fcd7f0cb (diff)
Deduplicate conditional formats loaded from .ods
If there are several separate conditional format elements that can be represented as just one (with several ranges), try to do that. A particular customer document used to take 3 minutes 20 seconds to load, and it contained so many (tens of thousands) conditional formats that the Format> Conditional Formatting> Manage... dialog was practically impossible to use. Now loading that document takes 15 seconds and there are just a handful of separate conditional formats. Also add a simple unit test to verify the deduplication. Change-Id: I7c468af99956d4646ee5507390f1476caff52325 Reviewed-on: https://gerrit.libreoffice.org/45460 Tested-by: Jenkins <ci@libreoffice.org> Reviewed-by: Tor Lillqvist <tml@collabora.com>
-rw-r--r--sc/CppunitTest_sc_cond_format_merge.mk116
-rw-r--r--sc/Module_sc.mk1
-rw-r--r--sc/inc/conditio.hxx3
-rw-r--r--sc/qa/extras/testdocuments/cond_format_merge.odsbin0 -> 8428 bytes
-rw-r--r--sc/qa/unit/cond_format_merge.cxx155
-rw-r--r--sc/source/core/data/conditio.cxx19
-rw-r--r--sc/source/filter/xml/xmlcondformat.cxx229
-rw-r--r--sc/source/filter/xml/xmlcondformat.hxx25
8 files changed, 542 insertions, 6 deletions
diff --git a/sc/CppunitTest_sc_cond_format_merge.mk b/sc/CppunitTest_sc_cond_format_merge.mk
new file mode 100644
index 000000000000..bfb7dc2bba3f
--- /dev/null
+++ b/sc/CppunitTest_sc_cond_format_merge.mk
@@ -0,0 +1,116 @@
+# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*-
+#
+# This file is part of the LibreOffice project.
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+#
+
+$(eval $(call gb_CppunitTest_CppunitTest,sc_cond_format_merge))
+
+$(eval $(call gb_CppunitTest_add_exception_objects,sc_cond_format_merge, \
+ sc/qa/unit/cond_format_merge \
+))
+
+$(eval $(call gb_CppunitTest_use_externals,sc_cond_format_merge, \
+ boost_headers \
+))
+
+$(eval $(call gb_CppunitTest_use_libraries,sc_cond_format_merge, \
+ basegfx \
+ comphelper \
+ cppu \
+ cppuhelper \
+ drawinglayer \
+ editeng \
+ for \
+ forui \
+ i18nlangtag \
+ msfilter \
+ oox \
+ sal \
+ salhelper \
+ sax \
+ sb \
+ sc \
+ scqahelper \
+ sfx \
+ sot \
+ subsequenttest \
+ svl \
+ svt \
+ svx \
+ svxcore \
+ test \
+ tk \
+ tl \
+ ucbhelper \
+ unotest \
+ utl \
+ vbahelper \
+ vcl \
+ xo \
+))
+
+$(eval $(call gb_CppunitTest_set_include,sc_cond_format_merge,\
+ -I$(SRCDIR)/sc/source/ui/inc \
+ -I$(SRCDIR)/sc/inc \
+ $$(INCLUDE) \
+))
+
+$(eval $(call gb_CppunitTest_use_sdk_api,sc_cond_format_merge))
+
+$(eval $(call gb_CppunitTest_use_ure,sc_cond_format_merge))
+$(eval $(call gb_CppunitTest_use_vcl,sc_cond_format_merge))
+
+$(eval $(call gb_CppunitTest_use_components,sc_cond_format_merge,\
+ basic/util/sb \
+ chart2/source/chartcore \
+ chart2/source/controller/chartcontroller \
+ comphelper/util/comphelp \
+ configmgr/source/configmgr \
+ dbaccess/util/dba \
+ embeddedobj/util/embobj \
+ eventattacher/source/evtatt \
+ filter/source/config/cache/filterconfig1 \
+ filter/source/storagefilterdetect/storagefd \
+ forms/util/frm \
+ framework/util/fwk \
+ i18npool/util/i18npool \
+ oox/util/oox \
+ package/source/xstor/xstor \
+ package/util/package2 \
+ sax/source/expatwrap/expwrap \
+ scaddins/source/analysis/analysis \
+ scaddins/source/datefunc/date \
+ scripting/source/basprov/basprov \
+ scripting/util/scriptframe \
+ sc/util/sc \
+ sc/util/scd \
+ sc/util/scfilt \
+ $(call gb_Helper_optional,SCRIPTING, \
+ sc/util/vbaobj) \
+ sfx2/util/sfx \
+ sot/util/sot \
+ svl/source/fsstor/fsstorage \
+ svl/util/svl \
+ svtools/util/svt \
+ svx/util/svx \
+ svx/util/svxcore \
+ toolkit/util/tk \
+ ucb/source/core/ucb1 \
+ ucb/source/ucp/file/ucpfile1 \
+ ucb/source/ucp/tdoc/ucptdoc1 \
+ unotools/util/utl \
+ unoxml/source/rdf/unordf \
+ unoxml/source/service/unoxml \
+ uui/util/uui \
+ xmloff/util/xo \
+))
+
+$(eval $(call gb_CppunitTest_use_configuration,sc_cond_format_merge))
+
+$(eval $(call gb_CppunitTest_use_unittest_configuration,sc_cond_format_merge))
+
+# vim: set noet sw=4 ts=4:
diff --git a/sc/Module_sc.mk b/sc/Module_sc.mk
index 55f2dfd19b0b..6e30bd22c1f0 100644
--- a/sc/Module_sc.mk
+++ b/sc/Module_sc.mk
@@ -57,6 +57,7 @@ endif
endif
$(eval $(call gb_Module_add_slowcheck_targets,sc, \
+ CppunitTest_sc_cond_format_merge \
CppunitTest_sc_new_cond_format_api \
CppunitTest_sc_subsequent_filters_test \
CppunitTest_sc_subsequent_export_test \
diff --git a/sc/inc/conditio.hxx b/sc/inc/conditio.hxx
index dede939a9185..93481f202236 100644
--- a/sc/inc/conditio.hxx
+++ b/sc/inc/conditio.hxx
@@ -352,6 +352,8 @@ public:
bool operator== ( const ScConditionEntry& r ) const;
+ bool EqualIgnoringSrcPos( const ScConditionEntry& r ) const;
+
virtual void SetParent( ScConditionalFormat* pNew ) override;
bool IsCellValid( ScRefCellValue& rCell, const ScAddress& rPos ) const;
@@ -360,6 +362,7 @@ public:
void SetOperation(ScConditionMode eMode);
bool IsIgnoreBlank() const { return ( nOptions & SC_COND_NOBLANKS ) == 0; }
void SetIgnoreBlank(bool bSet);
+ OUString GetSrcString() const { return aSrcString; }
const ScAddress& GetSrcPos() const { return aSrcPos; }
ScAddress GetValidSrcPos() const; // adjusted to allow textual representation of expressions
diff --git a/sc/qa/extras/testdocuments/cond_format_merge.ods b/sc/qa/extras/testdocuments/cond_format_merge.ods
new file mode 100644
index 000000000000..43b676d22080
--- /dev/null
+++ b/sc/qa/extras/testdocuments/cond_format_merge.ods
Binary files differ
diff --git a/sc/qa/unit/cond_format_merge.cxx b/sc/qa/unit/cond_format_merge.cxx
new file mode 100644
index 000000000000..0ce3f21909bd
--- /dev/null
+++ b/sc/qa/unit/cond_format_merge.cxx
@@ -0,0 +1,155 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+#include <sal/config.h>
+
+#include <com/sun/star/beans/XPropertySet.hpp>
+#include <com/sun/star/lang/XComponent.hpp>
+#include <com/sun/star/sheet/XConditionalFormats.hpp>
+#include <com/sun/star/sheet/XSpreadsheet.hpp>
+#include <test/bootstrapfixture.hxx>
+#include <test/calc_unoapi_test.hxx>
+
+#include <global.hxx>
+#include <document.hxx>
+
+#include "helper/qahelper.hxx"
+
+using namespace css;
+
+class ScCondFormatMergeTest : public CalcUnoApiTest
+{
+public:
+ ScCondFormatMergeTest();
+
+ void testCondFormatMerge();
+
+ CPPUNIT_TEST_SUITE(ScCondFormatMergeTest);
+ CPPUNIT_TEST(testCondFormatMerge);
+ CPPUNIT_TEST_SUITE_END();
+};
+
+ScCondFormatMergeTest::ScCondFormatMergeTest()
+ : CalcUnoApiTest("sc/qa/extras/testdocuments/")
+{
+}
+
+void ScCondFormatMergeTest::testCondFormatMerge()
+{
+ OUString aFileURL;
+ createFileURL("cond_format_merge.ods", aFileURL);
+ uno::Reference<lang::XComponent> mxComponent = loadFromDesktop(aFileURL);
+
+ CPPUNIT_ASSERT_MESSAGE("Component not loaded", mxComponent.is());
+
+ // get the first sheet
+ uno::Reference<sheet::XSpreadsheetDocument> xDoc(mxComponent, uno::UNO_QUERY_THROW);
+ uno::Reference<container::XIndexAccess> xIndex(xDoc->getSheets(), uno::UNO_QUERY_THROW);
+ uno::Reference<sheet::XSpreadsheet> xSheet(xIndex->getByIndex(0), uno::UNO_QUERY_THROW);
+
+ uno::Reference<beans::XPropertySet> xProps(xSheet, uno::UNO_QUERY_THROW);
+ uno::Any aAny = xProps->getPropertyValue("ConditionalFormats");
+ uno::Reference<sheet::XConditionalFormats> xCondFormats;
+
+ CPPUNIT_ASSERT(aAny >>= xCondFormats);
+ CPPUNIT_ASSERT(xCondFormats.is());
+
+ CPPUNIT_ASSERT_EQUAL(sal_Int32(5), xCondFormats->getLength());
+
+ uno::Sequence<uno::Reference<sheet::XConditionalFormat>> xCondFormatSeq
+ = xCondFormats->getConditionalFormats();
+ CPPUNIT_ASSERT_EQUAL(sal_Int32(5), xCondFormatSeq.getLength());
+
+ int nRanges = 0;
+ for (sal_Int32 i = 0, n = xCondFormatSeq.getLength(); i < n; ++i)
+ {
+ CPPUNIT_ASSERT(xCondFormatSeq[i].is());
+
+ uno::Reference<sheet::XConditionalFormat> xCondFormat = xCondFormatSeq[i];
+ CPPUNIT_ASSERT(xCondFormat.is());
+
+ uno::Reference<beans::XPropertySet> xPropSet(xCondFormat, uno::UNO_QUERY_THROW);
+
+ aAny = xPropSet->getPropertyValue("Range");
+ uno::Reference<sheet::XSheetCellRanges> xCellRanges;
+ CPPUNIT_ASSERT(aAny >>= xCellRanges);
+ CPPUNIT_ASSERT(xCellRanges.is());
+
+ uno::Sequence<table::CellRangeAddress> aRanges = xCellRanges->getRangeAddresses();
+ CPPUNIT_ASSERT_GREATEREQUAL(sal_Int32(1), aRanges.getLength());
+
+ table::CellRangeAddress aRange0 = aRanges[0];
+ CPPUNIT_ASSERT_EQUAL(sal_Int16(0), aRange0.Sheet);
+ CPPUNIT_ASSERT_EQUAL(aRange0.StartColumn, aRange0.EndColumn);
+
+ table::CellRangeAddress aRange1;
+
+ switch (aRange0.StartColumn)
+ {
+ case 3:
+ switch (aRange0.StartRow)
+ {
+ case 0: // D1:D2,D5::D8
+ nRanges++;
+ CPPUNIT_ASSERT_EQUAL(sal_Int32(1), aRange0.EndRow);
+ CPPUNIT_ASSERT_EQUAL(sal_Int32(2), aRanges.getLength());
+ aRange1 = aRanges[1];
+ CPPUNIT_ASSERT_EQUAL(sal_Int16(0), aRange1.Sheet);
+ CPPUNIT_ASSERT_EQUAL(aRange1.StartColumn, aRange1.EndColumn);
+ CPPUNIT_ASSERT_EQUAL(sal_Int32(3), aRange1.StartColumn);
+ CPPUNIT_ASSERT_EQUAL(sal_Int32(4), aRange1.StartRow);
+ CPPUNIT_ASSERT_EQUAL(sal_Int32(7), aRange1.EndRow);
+ break;
+ default:
+ CPPUNIT_FAIL("Unexpected range in column D");
+ }
+ break;
+ case 5:
+ switch (aRange0.StartRow)
+ {
+ case 0: // F1:F2
+ nRanges++;
+ CPPUNIT_ASSERT_EQUAL(sal_Int32(1), aRange0.EndRow);
+ CPPUNIT_ASSERT_EQUAL(sal_Int32(1), aRanges.getLength());
+ break;
+ case 2: // F3
+ nRanges++;
+ CPPUNIT_ASSERT_EQUAL(sal_Int32(2), aRange0.EndRow);
+ CPPUNIT_ASSERT_EQUAL(sal_Int32(1), aRanges.getLength());
+ break;
+ case 3: // F4
+ nRanges++;
+ CPPUNIT_ASSERT_EQUAL(sal_Int32(3), aRange0.EndRow);
+ CPPUNIT_ASSERT_EQUAL(sal_Int32(1), aRanges.getLength());
+ break;
+ case 4: // F5
+ nRanges++;
+ CPPUNIT_ASSERT_EQUAL(sal_Int32(4), aRange0.EndRow);
+ CPPUNIT_ASSERT_EQUAL(sal_Int32(1), aRanges.getLength());
+ break;
+ default:
+ CPPUNIT_FAIL("Unexpected range in column F");
+ }
+ break;
+ default:
+ CPPUNIT_FAIL("Unexpected range");
+ }
+ }
+
+ CPPUNIT_ASSERT_EQUAL(5, nRanges);
+
+ closeDocument(mxComponent);
+ mxComponent.clear();
+}
+
+CPPUNIT_TEST_SUITE_REGISTRATION(ScCondFormatMergeTest);
+
+CPPUNIT_PLUGIN_IMPLEMENT();
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/core/data/conditio.cxx b/sc/source/core/data/conditio.cxx
index ca6b49f20a75..5be3688bfe6a 100644
--- a/sc/source/core/data/conditio.cxx
+++ b/sc/source/core/data/conditio.cxx
@@ -650,6 +650,25 @@ bool ScConditionEntry::operator== ( const ScConditionEntry& r ) const
return bEq;
}
+bool ScConditionEntry::EqualIgnoringSrcPos( const ScConditionEntry& r ) const
+{
+ bool bEq = (eOp == r.eOp && nOptions == r.nOptions &&
+ lcl_IsEqual( pFormula1, r.pFormula1 ) &&
+ lcl_IsEqual( pFormula2, r.pFormula2 ));
+ if (bEq)
+ {
+ // Here, ignore the aSrcPoses and aSrcStrings
+
+ // If not formulas, compare values
+ if ( !pFormula1 && ( nVal1 != r.nVal1 || aStrVal1 != r.aStrVal1 || bIsStr1 != r.bIsStr1 ) )
+ bEq = false;
+ if ( !pFormula2 && ( nVal2 != r.nVal2 || aStrVal2 != r.aStrVal2 || bIsStr2 != r.bIsStr2 ) )
+ bEq = false;
+ }
+
+ return bEq;
+}
+
void ScConditionEntry::Interpret( const ScAddress& rPos )
{
// Create formula cells
diff --git a/sc/source/filter/xml/xmlcondformat.cxx b/sc/source/filter/xml/xmlcondformat.cxx
index 48af2921775a..2e8a2a66d1a3 100644
--- a/sc/source/filter/xml/xmlcondformat.cxx
+++ b/sc/source/filter/xml/xmlcondformat.cxx
@@ -21,6 +21,7 @@
#include <docfunc.hxx>
#include "XMLConverter.hxx"
#include <stylehelper.hxx>
+#include <tokenarray.hxx>
using namespace xmloff::token;
@@ -41,7 +42,7 @@ css::uno::Reference< css::xml::sax::XFastContextHandler > SAL_CALL ScXMLConditio
switch (nElement)
{
case XML_ELEMENT( CALC_EXT, XML_CONDITIONAL_FORMAT ):
- pContext = new ScXMLConditionalFormatContext( GetScImport(), pAttribList );
+ pContext = new ScXMLConditionalFormatContext( GetScImport(), pAttribList, *this );
break;
}
@@ -57,11 +58,18 @@ void SAL_CALL ScXMLConditionalFormatsContext::endFastElement( sal_Int32 /*nEleme
bool bDeleted = !pCondFormatList->CheckAllEntries();
SAL_WARN_IF(bDeleted, "sc", "conditional formats have been deleted because they contained empty range info");
+
+ for (const auto& i : mvCondFormatData)
+ {
+ pDoc->AddCondFormatData( i.mpFormat->GetRange(), i.mnTab, i.mpFormat->GetKey() );
+ }
}
ScXMLConditionalFormatContext::ScXMLConditionalFormatContext( ScXMLImport& rImport,
- const rtl::Reference<sax_fastparser::FastAttributeList>& rAttrList ):
- ScXMLImportContext( rImport )
+ const rtl::Reference<sax_fastparser::FastAttributeList>& rAttrList,
+ ScXMLConditionalFormatsContext& rParent ):
+ ScXMLImportContext( rImport ),
+ mrParent( rParent )
{
OUString sRange;
@@ -118,16 +126,227 @@ css::uno::Reference< css::xml::sax::XFastContextHandler > SAL_CALL ScXMLConditio
return pContext;
}
+static bool HasRelRefIgnoringSheet0Relative( ScDocument* pDoc, const ScTokenArray* pTokens, sal_uInt16 nRecursion = 0 )
+{
+ if (pTokens)
+ {
+ formula::FormulaTokenArrayPlainIterator aIter( *pTokens );
+ formula::FormulaToken* t;
+ for( t = aIter.Next(); t; t = aIter.Next() )
+ {
+ switch( t->GetType() )
+ {
+ case formula::svDoubleRef:
+ {
+ ScSingleRefData& rRef2 = t->GetDoubleRef()->Ref2;
+ if ( rRef2.IsColRel() || rRef2.IsRowRel() || (rRef2.IsFlag3D() && rRef2.IsTabRel()) )
+ return true;
+ SAL_FALLTHROUGH;
+ }
+
+ case formula::svSingleRef:
+ {
+ ScSingleRefData& rRef1 = *t->GetSingleRef();
+ if ( rRef1.IsColRel() || rRef1.IsRowRel() || (rRef1.IsFlag3D() && rRef1.IsTabRel()) )
+ return true;
+ }
+ break;
+
+ case formula::svIndex:
+ {
+ if( t->GetOpCode() == ocName ) // DB areas always absolute
+ if( ScRangeData* pRangeData = pDoc->FindRangeNameBySheetAndIndex( t->GetSheet(), t->GetIndex()) )
+ if( (nRecursion < 42) && HasRelRefIgnoringSheet0Relative( pDoc, pRangeData->GetCode(), nRecursion + 1 ) )
+ return true;
+ }
+ break;
+
+ // #i34474# function result dependent on cell position
+ case formula::svByte:
+ {
+ switch( t->GetOpCode() )
+ {
+ case ocRow: // ROW() returns own row index
+ case ocColumn: // COLUMN() returns own column index
+ case ocSheet: // SHEET() returns own sheet index
+ case ocCell: // CELL() may return own cell address
+ return true;
+ default:
+ break;
+ }
+ }
+ break;
+
+ default:
+ break;
+ }
+ }
+ }
+ return false;
+}
+
+static bool HasOneSingleFullyRelativeReference( const ScTokenArray* pTokens, ScSingleRefData& rOffset )
+{
+ int nCount = 0;
+ if (pTokens)
+ {
+ formula::FormulaTokenArrayPlainIterator aIter( *pTokens );
+ formula::FormulaToken* t;
+ for( t = aIter.Next(); t; t = aIter.Next() )
+ {
+ switch( t->GetType() )
+ {
+ case formula::svSingleRef:
+ {
+ ScSingleRefData& rRef1 = *t->GetSingleRef();
+ if ( rRef1.IsColRel() && rRef1.IsRowRel() && !rRef1.IsFlag3D() && rRef1.IsTabRel() )
+ {
+ nCount++;
+ if (nCount == 1)
+ {
+ rOffset = rRef1;
+ }
+ }
+ }
+ break;
+
+ default:
+ break;
+ }
+ }
+ }
+ return nCount == 1;
+}
+
void SAL_CALL ScXMLConditionalFormatContext::endFastElement( sal_Int32 /*nElement*/ )
{
ScDocument* pDoc = GetScImport().GetDocument();
SCTAB nTab = GetScImport().GetTables().GetCurrentSheet();
ScConditionalFormat* pFormat = mxFormat.release();
+
+ bool bEligibleForCache = true;
+ bool bSingleRelativeReference = false;
+ ScSingleRefData aOffsetForSingleRelRef;
+ const ScTokenArray* pTokens = nullptr;
+ for (size_t nFormatEntryIx = 0; nFormatEntryIx < pFormat->size(); ++nFormatEntryIx)
+ {
+ auto pFormatEntry = pFormat->GetEntry(nFormatEntryIx);
+ auto pCondFormatEntry = static_cast<const ScCondFormatEntry*>(pFormatEntry);
+
+ if (pCondFormatEntry->GetOperation() != ScConditionMode::Equal &&
+ pCondFormatEntry->GetOperation() != ScConditionMode::Direct)
+ {
+ bEligibleForCache = false;
+ break;
+ }
+
+ ScAddress aSrcPos;
+ OUString aSrcString = pCondFormatEntry->GetSrcString();
+ if ( !aSrcString.isEmpty() )
+ aSrcPos.Parse( aSrcString, pDoc );
+ ScCompiler aComp( pDoc, aSrcPos );
+ aComp.SetGrammar( formula::FormulaGrammar::GRAM_ODFF );
+ pTokens = aComp.CompileString( pCondFormatEntry->GetExpression(aSrcPos, 0), "" );
+ if (HasRelRefIgnoringSheet0Relative( pDoc, pTokens ))
+ {
+ // In general not eligible, but some might be. We handle one very special case: When the
+ // conditional format has one entry, the reference position is the first cell of the
+ // range, and with a single fully relative reference in its expression. (Possibly these
+ // conditions could be loosened, but I am too tired to think on that right now.)
+ if (pFormat->size() == 1 &&
+ pFormat->GetRange().size() == 1 &&
+ pFormat->GetRange()[0]->aStart == aSrcPos &&
+ HasOneSingleFullyRelativeReference( pTokens, aOffsetForSingleRelRef ))
+ {
+ bSingleRelativeReference = true;
+ }
+ else
+ {
+ bEligibleForCache = false;
+ break;
+ }
+ }
+ }
+
+ if (bEligibleForCache)
+ {
+ for (auto& aCacheEntry : mrParent.maCache)
+ if (aCacheEntry.mnAge < SAL_MAX_INT64)
+ aCacheEntry.mnAge++;
+
+ for (auto& aCacheEntry : mrParent.maCache)
+ {
+ if (!aCacheEntry.mpFormat)
+ continue;
+
+ if (aCacheEntry.mpFormat->size() != pFormat->size())
+ continue;
+
+ // Check if the conditional format is identical to an existing one (but with different range) and can be shared
+ for (size_t nFormatEntryIx = 0; nFormatEntryIx < pFormat->size(); ++nFormatEntryIx)
+ {
+ auto pCacheFormatEntry = aCacheEntry.mpFormat->GetEntry(nFormatEntryIx);
+ auto pFormatEntry = pFormat->GetEntry(nFormatEntryIx);
+ if (pCacheFormatEntry->GetType() != pFormatEntry->GetType() ||
+ pFormatEntry->GetType() != ScFormatEntry::Type::Condition)
+ break;
+
+ auto pCacheCondFormatEntry = static_cast<const ScCondFormatEntry*>(pCacheFormatEntry);
+ auto pCondFormatEntry = static_cast<const ScCondFormatEntry*>(pFormatEntry);
+
+ if (pCacheCondFormatEntry->GetStyle() != pCondFormatEntry->GetStyle())
+ break;
+
+ // Note That comparing the formulas of the ScConditionEntry at this stage is
+ // comparing just the *strings* of the formulas. For the bSingleRelativeReference
+ // case we compare the tokenized ("compiled") formulas.
+ if (bSingleRelativeReference)
+ {
+ if (aCacheEntry.mbSingleRelativeReference &&
+ pTokens->EqualTokens(aCacheEntry.mpTokens.get()))
+ ;
+ else
+ break;
+ }
+ else if (!pCacheCondFormatEntry->EqualIgnoringSrcPos(*pCondFormatEntry))
+ {
+ break;
+ }
+ // If we get here on the last round through the for loop, we have a cache hit
+ if (nFormatEntryIx == pFormat->size() - 1)
+ {
+ // Mark cache entry as fresh, do necessary mangling of it and just return
+ aCacheEntry.mnAge = 0;
+ for (size_t k = 0; k < pFormat->GetRange().size(); ++k)
+ aCacheEntry.mpFormat->GetRangeList().Join(*(pFormat->GetRange()[k]));
+ return;
+ }
+ }
+ }
+
+ // Not found in cache, replace oldest cache entry
+ sal_Int64 nOldestAge = -1;
+ size_t nIndexOfOldest = 0;
+ for (auto& aCacheEntry : mrParent.maCache)
+ {
+ if (aCacheEntry.mnAge > nOldestAge)
+ {
+ nOldestAge = aCacheEntry.mnAge;
+ nIndexOfOldest = (&aCacheEntry - &mrParent.maCache.front());
+ }
+ }
+ mrParent.maCache[nIndexOfOldest].mpFormat = pFormat;
+ mrParent.maCache[nIndexOfOldest].mbSingleRelativeReference = bSingleRelativeReference;
+ mrParent.maCache[nIndexOfOldest].mpTokens.reset(pTokens);
+ mrParent.maCache[nIndexOfOldest].mnAge = 0;
+ }
+
sal_uLong nIndex = pDoc->AddCondFormat(pFormat, nTab);
- pFormat->SetKey(nIndex);
+ (void) nIndex; // Avoid 'unused variable' warning when assert() expands to empty
+ assert(pFormat->GetKey() == nIndex);
- pDoc->AddCondFormatData( pFormat->GetRange(), nTab, nIndex);
+ mrParent.mvCondFormatData.push_back( { pFormat, nTab } );
}
ScXMLConditionalFormatContext::~ScXMLConditionalFormatContext()
diff --git a/sc/source/filter/xml/xmlcondformat.hxx b/sc/source/filter/xml/xmlcondformat.hxx
index 6232bfc381af..fc253081ff0d 100644
--- a/sc/source/filter/xml/xmlcondformat.hxx
+++ b/sc/source/filter/xml/xmlcondformat.hxx
@@ -10,6 +10,7 @@
#ifndef INCLUDED_SC_SOURCE_FILTER_XML_XMLCONDFORMAT_HXX
#define INCLUDED_SC_SOURCE_FILTER_XML_XMLCONDFORMAT_HXX
+#include <array>
#include <memory>
#include <xmloff/xmlictxt.hxx>
#include "xmlimprt.hxx"
@@ -25,6 +26,21 @@ struct ScIconSetFormatData;
class ScXMLConditionalFormatsContext : public ScXMLImportContext
{
+private:
+ struct CacheEntry
+ {
+ ScConditionalFormat* mpFormat = nullptr;
+ bool mbSingleRelativeReference;
+ std::unique_ptr<const ScTokenArray> mpTokens;
+ sal_Int64 mnAge = SAL_MAX_INT64;
+ };
+
+ struct CondFormatData
+ {
+ ScConditionalFormat* mpFormat;
+ SCTAB mnTab;
+ };
+
public:
ScXMLConditionalFormatsContext( ScXMLImport& rImport );
@@ -32,13 +48,18 @@ public:
sal_Int32 nElement, const css::uno::Reference< css::xml::sax::XFastAttributeList >& xAttrList ) override;
virtual void SAL_CALL endFastElement( sal_Int32 nElement ) override;
+
+ std::array<CacheEntry, 4> maCache;
+
+ std::vector<CondFormatData> mvCondFormatData;
};
class ScXMLConditionalFormatContext : public ScXMLImportContext
{
public:
ScXMLConditionalFormatContext( ScXMLImport& rImport,
- const rtl::Reference<sax_fastparser::FastAttributeList>& rAttrList );
+ const rtl::Reference<sax_fastparser::FastAttributeList>& rAttrList,
+ ScXMLConditionalFormatsContext& rParent );
virtual ~ScXMLConditionalFormatContext() override;
@@ -50,6 +71,8 @@ private:
std::unique_ptr<ScConditionalFormat> mxFormat;
ScRangeList maRange;
+
+ ScXMLConditionalFormatsContext& mrParent;
};
class ScXMLColorScaleFormatContext : public ScXMLImportContext