[Libreoffice-commits] core.git: Branch 'private/swe/libreoffice-5-2+backports' - sc/CppunitTest_sc_cond_format_merge.mk sc/inc sc/Module_sc.mk sc/qa sc/source

Tor Lillqvist tml at collabora.com
Fri Dec 1 01:19:02 UTC 2017


 sc/CppunitTest_sc_cond_format_merge.mk           |  116 +++++++++++
 sc/Module_sc.mk                                  |    1 
 sc/inc/conditio.hxx                              |    3 
 sc/qa/extras/testdocuments/cond_format_merge.ods |binary
 sc/qa/unit/cond_format_merge.cxx                 |  155 +++++++++++++++
 sc/source/core/data/conditio.cxx                 |   19 +
 sc/source/filter/xml/xmlcondformat.cxx           |  227 ++++++++++++++++++++++-
 sc/source/filter/xml/xmlcondformat.hxx           |   26 ++
 8 files changed, 541 insertions(+), 6 deletions(-)

New commits:
commit 7540cdbc007c937f39db65bd5ed9280b0f759e32
Author: Tor Lillqvist <tml at collabora.com>
Date:   Sun Nov 26 23:28:05 2017 +0200

    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/45479
    Reviewed-by: Thorsten Behrens <Thorsten.Behrens at CIB.de>
    Tested-by: Thorsten Behrens <Thorsten.Behrens at CIB.de>

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 7175f01a2658..26f967d7b267 100644
--- a/sc/Module_sc.mk
+++ b/sc/Module_sc.mk
@@ -52,6 +52,7 @@ $(eval $(call gb_Module_add_check_targets,sc,\
 ))
 
 $(eval $(call gb_Module_add_slowcheck_targets,sc, \
+	CppunitTest_sc_cond_format_merge \
 	CppunitTest_sc_condformats \
 	CppunitTest_sc_new_cond_format_api \
 	CppunitTest_sc_subsequent_filters_test \
diff --git a/sc/inc/conditio.hxx b/sc/inc/conditio.hxx
index 104fe4293ebf..2f6b266f7468 100644
--- a/sc/inc/conditio.hxx
+++ b/sc/inc/conditio.hxx
@@ -233,6 +233,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;
@@ -241,6 +243,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
Binary files /dev/null and b/sc/qa/extras/testdocuments/cond_format_merge.ods 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 68fa98beb206..d3d996d93239 100644
--- a/sc/source/core/data/conditio.cxx
+++ b/sc/source/core/data/conditio.cxx
@@ -694,6 +694,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 7e0e0c063ce1..f9704a1afb09 100644
--- a/sc/source/filter/xml/xmlcondformat.cxx
+++ b/sc/source/filter/xml/xmlcondformat.cxx
@@ -19,6 +19,7 @@
 #include "docfunc.hxx"
 #include "XMLConverter.hxx"
 #include "stylehelper.hxx"
+#include "tokenarray.hxx"
 
 ScXMLConditionalFormatsContext::ScXMLConditionalFormatsContext( ScXMLImport& rImport, sal_uInt16 nPrfx,
                         const OUString& rLName):
@@ -38,7 +39,7 @@ SvXMLImportContext* ScXMLConditionalFormatsContext::CreateChildContext( sal_uInt
     switch (nToken)
     {
         case XML_TOK_CONDFORMATS_CONDFORMAT:
-            pContext = new ScXMLConditionalFormatContext( GetScImport(), nPrefix, rLocalName, xAttrList );
+            pContext = new ScXMLConditionalFormatContext( GetScImport(), nPrefix, rLocalName, xAttrList, *this );
             break;
     }
 
@@ -54,11 +55,18 @@ void ScXMLConditionalFormatsContext::EndElement()
     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, sal_uInt16 nPrfx,
-                        const OUString& rLName, const css::uno::Reference< css::xml::sax::XAttributeList>& xAttrList):
-    SvXMLImportContext( rImport, nPrfx, rLName )
+                        const OUString& rLName, const css::uno::Reference< css::xml::sax::XAttributeList>& xAttrList,
+                        ScXMLConditionalFormatsContext& rParent ):
+    SvXMLImportContext( rImport, nPrfx, rLName ),
+    mrParent( rParent )
 {
     OUString sRange;
 
@@ -120,16 +128,225 @@ SvXMLImportContext* ScXMLConditionalFormatContext::CreateChildContext( sal_uInt1
     return pContext;
 }
 
+static bool HasRelRefIgnoringSheet0Relative( ScDocument* pDoc, ScTokenArray* pTokens, sal_uInt16 nRecursion = 0 )
+{
+    if (pTokens)
+    {
+        formula::FormulaToken* t;
+        for( t = pTokens->First(); t; t = pTokens->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( ScTokenArray* pTokens, ScSingleRefData& rOffset )
+{
+    int nCount = 0;
+    if (pTokens)
+    {
+        formula::FormulaToken* t;
+        for( t = pTokens->First(); t; t = pTokens->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 ScXMLConditionalFormatContext::EndElement()
 {
     ScDocument* pDoc = GetScImport().GetDocument();
 
     SCTAB nTab = GetScImport().GetTables().GetCurrentSheet();
     ScConditionalFormat* pFormat = mxFormat.release();
+
+    bool bEligibleForCache = true;
+    bool bSingleRelativeReference = false;
+    ScSingleRefData aOffsetForSingleRelRef;
+    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() != SC_COND_EQUAL &&
+            pCondFormatEntry->GetOperation() != SC_COND_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() != condformat::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 ca42c0b6eed5..7a94d6d7fe4a 100644
--- a/sc/source/filter/xml/xmlcondformat.hxx
+++ b/sc/source/filter/xml/xmlcondformat.hxx
@@ -10,9 +10,11 @@
 #ifndef INCLUDED_SC_SOURCE_FILTER_XML_XMLCONDFORMAT_HXX
 #define INCLUDED_SC_SOURCE_FILTER_XML_XMLCONDFORMAT_HXX
 
+#include <array>
 #include <xmloff/xmlictxt.hxx>
 #include "xmlimprt.hxx"
 #include "rangelst.hxx"
+#include "tokenarray.hxx"
 
 class ScColorScaleFormat;
 class ScColorScaleEntry;
@@ -23,6 +25,21 @@ struct ScIconSetFormatData;
 
 class ScXMLConditionalFormatsContext : public SvXMLImportContext
 {
+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;
+    };
+
     const ScXMLImport& GetScImport() const { return static_cast<const ScXMLImport&>(GetImport()); }
     ScXMLImport& GetScImport() { return static_cast<ScXMLImport&>(GetImport()); }
 public:
@@ -36,6 +53,10 @@ public:
                                      const css::uno::Reference<css::xml::sax::XAttributeList>& xAttrList ) override;
 
     virtual void EndElement() override;
+
+    std::array<CacheEntry, 4> maCache;
+
+    std::vector<CondFormatData> mvCondFormatData;
 };
 
 class ScXMLConditionalFormatContext : public SvXMLImportContext
@@ -45,7 +66,8 @@ class ScXMLConditionalFormatContext : public SvXMLImportContext
 public:
     ScXMLConditionalFormatContext( ScXMLImport& rImport, sal_uInt16 nPrfx,
                         const OUString& rLName,
-                        const css::uno::Reference<css::xml::sax::XAttributeList>& xAttrList);
+                        const css::uno::Reference<css::xml::sax::XAttributeList>& xAttrList,
+                        ScXMLConditionalFormatsContext& rParent );
 
     virtual ~ScXMLConditionalFormatContext();
 
@@ -58,6 +80,8 @@ private:
 
     std::unique_ptr<ScConditionalFormat> mxFormat;
     ScRangeList maRange;
+
+    ScXMLConditionalFormatsContext& mrParent;
 };
 
 class ScXMLColorScaleFormatContext : public SvXMLImportContext


More information about the Libreoffice-commits mailing list