include/oox/drawingml/color.hxx              |    4 
 oox/source/drawingml/color.cxx               |   21 ++
 sc/qa/unit/subsequent_export_test4.cxx       |    6 
 sc/source/filter/inc/condformatbuffer.hxx    |   34 ++-
 sc/source/filter/inc/condformatcontext.hxx   |   10 -
 sc/source/filter/inc/numberformatsbuffer.hxx |    6 
 sc/source/filter/inc/stylesbuffer.hxx        |   25 ++
 sc/source/filter/oox/condformatbuffer.cxx    |  257 ++++++++++++++++++++++-----
 sc/source/filter/oox/condformatcontext.cxx   |   29 +--
 sc/source/filter/oox/stylesbuffer.cxx        |   61 ++++++
 10 files changed, 379 insertions(+), 74 deletions(-)

New commits:
commit e2cc1f8a6bd27907800416a5396942a0c7ca56ce
Author:     Noel Grandin <noel.gran...@collabora.co.uk>
AuthorDate: Fri Feb 28 10:51:44 2025 +0200
Commit:     Noel Grandin <noel.gran...@collabora.co.uk>
CommitDate: Fri Feb 28 13:16:46 2025 +0100

    tdf#134864 speedup load of pathological conditional formats in XLS
    
    This associated file had 300k conditional formatting entries, which
    reduced to about 10 when de-duplicated.
    
    Rework the logic so instead of deduplicating rule model entries, we 
dedplicate at a slightly higher level, and make the comparison check more 
thoroughly, which greatly increases the number of duplicates we find.
    
    Takes the load time from 38sec to 6sec on my machine.
    
    Tweak one of the unit tests, because we now consolidate some
    of the conditional formatting entries in that test file.
    
    Change-Id: Iff2c6afb1c9959576aa00a38d0a8e5d4a48b0fd5
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/182356
    Reviewed-by: Noel Grandin <noel.gran...@collabora.co.uk>
    Tested-by: Jenkins

diff --git a/include/oox/drawingml/color.hxx b/include/oox/drawingml/color.hxx
index dc8be778f665..7bafe0f8083e 100644
--- a/include/oox/drawingml/color.hxx
+++ b/include/oox/drawingml/color.hxx
@@ -130,6 +130,8 @@ public:
 
     model::ComplexColor getComplexColor() const;
 
+    bool operator==(const Color&) const;
+
 private:
     /** Internal helper for getColor(). */
     void                setResolvedRgb( ::Color nRgb ) const;
@@ -161,6 +163,8 @@ private:
         sal_Int32           mnValue;
 
         explicit            Transformation( sal_Int32 nToken, sal_Int32 nValue 
) : mnToken( nToken ), mnValue( nValue ) {}
+
+        bool operator==(const Transformation&) const noexcept = default;
     };
 
     mutable ColorMode   meMode;         /// Current color mode.
diff --git a/oox/source/drawingml/color.cxx b/oox/source/drawingml/color.cxx
index 8961618a10f7..b4a6f3ff36a7 100644
--- a/oox/source/drawingml/color.cxx
+++ b/oox/source/drawingml/color.cxx
@@ -299,6 +299,27 @@ Color::Color() :
 {
 }
 
+bool Color::operator==(const Color& rhs) const
+{
+    if (meMode != rhs.meMode)
+        return false;
+    if (maTransforms != rhs.maTransforms)
+        return false;
+    if (mnC1 != rhs.mnC1)
+        return false;
+    if (mnC2 != rhs.mnC2)
+        return false;
+    if (mnC3 != rhs.mnC3)
+        return false;
+    if (mnAlpha != rhs.mnAlpha)
+        return false;
+    if (msSchemeName != rhs.msSchemeName)
+        return false;
+    if (meThemeColorType != rhs.meThemeColorType)
+        return false;
+    return true;
+}
+
 ::Color Color::getDmlPresetColor( sal_Int32 nToken, ::Color nDefaultRgb )
 {
     /*  Do not pass nDefaultRgb to ContainerHelper::getVectorElement(), to be
diff --git a/sc/qa/unit/subsequent_export_test4.cxx 
b/sc/qa/unit/subsequent_export_test4.cxx
index a85a6e39d7ca..10cb1c9db8a5 100644
--- a/sc/qa/unit/subsequent_export_test4.cxx
+++ b/sc/qa/unit/subsequent_export_test4.cxx
@@ -1624,11 +1624,15 @@ CPPUNIT_TEST_FIXTURE(ScExportTest4, testTdf148820)
     xmlDocUniquePtr pSheet = parseExport(u"xl/worksheets/sheet1.xml"_ustr);
     CPPUNIT_ASSERT(pSheet);
 
+    CPPUNIT_ASSERT_EQUAL(u"5"_ustr,
+                         getXPathContent(pSheet, 
"count(/x:worksheet/x:conditionalFormatting)"));
+    CPPUNIT_ASSERT_EQUAL(
+        u"5"_ustr, getXPathContent(pSheet, 
"count(/x:worksheet/x:conditionalFormatting/x:cfRule)"));
     sal_Int32 nDxfIdCondFormatFirst
         = getXPath(pSheet, "/x:worksheet/x:conditionalFormatting[1]/x:cfRule", 
"dxfId").toInt32()
           + 1;
     sal_Int32 nDxfIdCondFormatLast
-        = getXPath(pSheet, 
"/x:worksheet/x:conditionalFormatting[20]/x:cfRule", "dxfId").toInt32()
+        = getXPath(pSheet, "/x:worksheet/x:conditionalFormatting[5]/x:cfRule", 
"dxfId").toInt32()
           + 1;
 
     xmlDocUniquePtr pStyles = parseExport(u"xl/styles.xml"_ustr);
diff --git a/sc/source/filter/inc/condformatbuffer.hxx 
b/sc/source/filter/inc/condformatbuffer.hxx
index ef60145a11fe..47f0080c2464 100644
--- a/sc/source/filter/inc/condformatbuffer.hxx
+++ b/sc/source/filter/inc/condformatbuffer.hxx
@@ -91,6 +91,8 @@ struct ColorScaleRuleModelEntry
         mbPercentile(false),
         mbNum(false),
         mbGreaterThanOrEqual(true) {}
+
+    bool operator==(const ColorScaleRuleModelEntry &) const = default;
 };
 
 class ColorScaleRule final : public WorksheetHelper
@@ -103,6 +105,9 @@ public:
 
     void AddEntries( ScColorScaleFormat* pFormat, ScDocument* pDoc, const 
ScAddress& rAddr );
 
+    const std::vector< ColorScaleRuleModelEntry > & getModelEntries() const { 
return maColorScaleRuleEntries; }
+    sal_uInt32 getCfvo() const { return mnCfvo; }
+    sal_uInt32 getCol() const { return mnCol; }
 private:
     std::vector< ColorScaleRuleModelEntry > maColorScaleRuleEntries;
 
@@ -147,10 +152,16 @@ private:
     bool mbCustom;
 };
 
-/** Represents a single rule in a conditional formatting. */
+/** Represents a single rule in a conditional formatting.
+    Unlike other objects, we hold this by unique_ptr.
+    We cannot use shared_ptr like the other objects, since
+    it wants to have a pointer to its parent,
+    and its parent might get deduplicated and deleted.
+*/
 class CondFormatRule final : public WorksheetHelper
 {
 friend class CondFormatBuffer;
+friend struct CondFormatEquals;
 public:
     explicit            CondFormatRule( const CondFormat& rCondFormat, 
ScConditionalFormat* pFormat );
 
@@ -171,10 +182,14 @@ public:
     /** Returns the priority of this rule. */
     sal_Int32    getPriority() const { return maModel.mnPriority; }
 
+    const CondFormatRuleModel & getRuleModel() const { return maModel; }
+
     ColorScaleRule*     getColorScale();
     DataBarRule*        getDataBar();
     IconSetRule*            getIconSet();
 
+    const CondFormat& getParentCondFormat() const { return mrCondFormat; }
+
 private:
     const CondFormat&   mrCondFormat;
     CondFormatRuleModel maModel;
@@ -185,8 +200,6 @@ private:
     std::unique_ptr<IconSetRule> mpIconSet;
 };
 
-typedef std::shared_ptr< CondFormatRule > CondFormatRuleRef;
-
 /** Model for a conditional formatting object. */
 struct CondFormatModel
 {
@@ -197,11 +210,15 @@ struct CondFormatModel
 };
 
 class CondFormatBuffer;
+struct CondFormatHash;
+struct CondFormatEquals;
 
 /** Represents a conditional formatting object with a list of affected cell 
ranges. */
 class CondFormat final : public WorksheetHelper
 {
 friend class CondFormatBuffer;
+friend struct CondFormatHash;
+friend struct CondFormatEquals;
 public:
     explicit            CondFormat( const WorksheetHelper& rHelper );
     ~CondFormat();
@@ -209,7 +226,7 @@ public:
     /** Imports settings from the conditionalFormatting element. */
     void                importConditionalFormatting( const AttributeList& 
rAttribs );
     /** Imports a conditional formatting rule from the cfRule element. */
-    CondFormatRuleRef   importCfRule( const AttributeList& rAttribs );
+    std::unique_ptr<CondFormatRule> importCfRule( const AttributeList& 
rAttribs );
 
     /** Imports settings from the CONDFORMATTING record. */
     void                importCondFormatting( SequenceInputStream& rStrm );
@@ -223,12 +240,12 @@ public:
     const ScRangeList& getRanges() const { return maModel.maRanges; }
 
     void                setReadyForFinalize() { mbReadyForFinalize = true; }
-    void                insertRule( CondFormatRuleRef const & xRule );
+    void                insertRule( std::unique_ptr<CondFormatRule>  xRule );
 private:
-    CondFormatRuleRef   createRule();
+    std::unique_ptr<CondFormatRule>   createRule();
 
 private:
-    typedef RefMap< sal_Int32, CondFormatRule > CondFormatRuleMap;
+    typedef std::map< sal_Int32, std::unique_ptr<CondFormatRule> > 
CondFormatRuleMap;
 
     CondFormatModel     maModel;            /// Model of this conditional 
formatting.
     CondFormatRuleMap   maRules;            /// Maps formatting rules by 
priority.
@@ -316,15 +333,16 @@ public:
     static sal_Int32    convertToApiOperator( sal_Int32 nToken );
     static ScConditionMode convertToInternalOperator( sal_Int32 nToken );
     void                finalizeImport();
-    bool                insertRule(CondFormatRef const & xCondFmt, 
CondFormatRuleRef const & xRule);
 
 private:
     CondFormatRef       createCondFormat();
     void                updateImport(const ScDataBarFormatData* pTarget);
+    void                deduplicateCondFormats();
 
 private:
     typedef RefVector< CondFormat > CondFormatVec;
     typedef RefVector< ExtCfDataBarRule > ExtCfDataBarRuleVec;
+
     CondFormatVec       maCondFormats;      /// All conditional formatting in 
a sheet.
     ExtCfDataBarRuleVec        maCfRules;          /// All external 
conditional formatting rules in a sheet.
     std::vector< std::unique_ptr<ExtCfCondFormat> > maExtCondFormats;
diff --git a/sc/source/filter/inc/condformatcontext.hxx 
b/sc/source/filter/inc/condformatcontext.hxx
index 351d2541b8ff..e406be4ddfac 100644
--- a/sc/source/filter/inc/condformatcontext.hxx
+++ b/sc/source/filter/inc/condformatcontext.hxx
@@ -29,25 +29,25 @@ class CondFormatContext;
 class ColorScaleContext final : public WorksheetContextBase
 {
 public:
-    explicit ColorScaleContext( CondFormatContext& rFragment, 
CondFormatRuleRef xRule );
+    explicit ColorScaleContext( CondFormatContext& rFragment, CondFormatRule& 
rRule );
 
     virtual ::oox::core::ContextHandlerRef onCreateContext( sal_Int32 
nElement, const AttributeList& rAttribs ) override;
     virtual void        onStartElement( const AttributeList& rAttribs ) 
override;
 
 private:
-    CondFormatRuleRef mxRule;
+    CondFormatRule& mrRule;
 };
 
 class DataBarContext final : public WorksheetContextBase
 {
 public:
-    explicit DataBarContext( CondFormatContext& rFormat, CondFormatRuleRef 
xRule );
+    explicit DataBarContext( CondFormatContext& rFormat, CondFormatRule& rRule 
);
 
     virtual ::oox::core::ContextHandlerRef onCreateContext( sal_Int32 
nElement, const AttributeList& rAttribs ) override;
     virtual void        onStartElement( const AttributeList& rAttribs ) 
override;
 
 private:
-    CondFormatRuleRef mxRule;
+    CondFormatRule& mrRule;
 };
 
 class IconSetContext final : public WorksheetContextBase
@@ -81,7 +81,7 @@ private:
     virtual void        onEndRecord() override;
 
     CondFormatRef       mxCondFmt;
-    CondFormatRuleRef   mxRule;
+    std::unique_ptr<CondFormatRule>   mxRule;
 };
 
 } // namespace oox::xls
diff --git a/sc/source/filter/inc/numberformatsbuffer.hxx 
b/sc/source/filter/inc/numberformatsbuffer.hxx
index 10ec0de1ce9e..bbd0d4fff20f 100644
--- a/sc/source/filter/inc/numberformatsbuffer.hxx
+++ b/sc/source/filter/inc/numberformatsbuffer.hxx
@@ -40,6 +40,8 @@ struct NumFmtModel
     sal_Int16           mnPredefId;
 
     explicit            NumFmtModel();
+
+    bool operator==(const NumFmtModel&) const = default;
 };
 
 /** Contains all API number format attributes. */
@@ -51,7 +53,7 @@ struct ApiNumFmtData
 };
 
 /** Contains all data for a number format code. */
-class NumberFormat : public WorkbookHelper
+class NumberFormat final : public WorkbookHelper
 {
 public:
     explicit            NumberFormat( const WorkbookHelper& rHelper );
@@ -73,6 +75,8 @@ public:
                             const css::lang::Locale& rFromLocale );
     sal_uInt32          fillToItemSet( SfxItemSet& rItemSet, bool 
bSkipPoolDefs = false ) const;
 
+    const NumFmtModel &  getModel() const { return maModel; }
+
 private:
     NumFmtModel         maModel;
     ApiNumFmtData       maApiData;
diff --git a/sc/source/filter/inc/stylesbuffer.hxx 
b/sc/source/filter/inc/stylesbuffer.hxx
index 95ec8b8806ff..224ed4ad92b3 100644
--- a/sc/source/filter/inc/stylesbuffer.hxx
+++ b/sc/source/filter/inc/stylesbuffer.hxx
@@ -154,6 +154,8 @@ struct FontModel
 
     explicit            FontModel();
 
+    bool operator==(const FontModel&) const;
+
     void                setBiff12Scheme( sal_uInt8 nScheme );
     void                setBiffHeight( sal_uInt16 nHeight );
     void                setBiffWeight( sal_uInt16 nWeight );
@@ -279,6 +281,8 @@ struct AlignmentModel
 
     explicit            AlignmentModel();
 
+    bool operator==(const AlignmentModel&) const = default;
+
     /** Sets horizontal alignment from the passed BIFF data. */
     void                setBiffHorAlign( sal_uInt8 nHorAlign );
     /** Sets vertical alignment from the passed BIFF data. */
@@ -339,6 +343,8 @@ struct ProtectionModel
     bool                mbLocked;           /// True = locked against editing.
     bool                mbHidden;           /// True = formula is hidden.
 
+    bool operator==(const ProtectionModel&) const = default;
+
     explicit            ProtectionModel();
 };
 
@@ -374,6 +380,8 @@ public:
     /** Returns the converted API protection data struct. */
     const ApiProtectionData& getApiData() const { return maApiData; }
 
+    const ProtectionModel& getModelData() const { return maModel; }
+
     void                fillToItemSet( SfxItemSet& rItemSet, bool 
bSkipPoolDefs = false ) const;
 private:
     ProtectionModel     maModel;            /// Protection model data.
@@ -392,6 +400,8 @@ struct BorderLineModel
 
     explicit            BorderLineModel( bool bDxf );
 
+    bool operator==(const BorderLineModel&) const;
+
     /** Sets the passed BIFF line style. */
     void                setBiffStyle( sal_Int32 nLineStyle );
 };
@@ -408,6 +418,8 @@ struct BorderModel
     bool                mbDiagBLtoTR;       /// True = bottom-left to 
top-right on.
 
     explicit            BorderModel( bool bDxf );
+
+    bool operator==(const BorderModel&) const = default;
 };
 
 /** Contains API attributes of a complete cell border. */
@@ -457,6 +469,8 @@ public:
     /** Returns the converted API border data struct. */
     const ApiBorderData& getApiData() const { return maApiData; }
 
+    const BorderModel& getModelData() const { return maModel; }
+
     void fillToItemSet( SfxItemSet& rItemSet, bool bSkipPoolDefs = false ) 
const;
 
 private:
@@ -489,6 +503,8 @@ struct PatternFillModel
 
     explicit            PatternFillModel( bool bDxf );
 
+    bool operator==(const PatternFillModel&) const;
+
     /** Sets the passed BIFF pattern identifier. */
     void                setBiffPattern( sal_Int32 nPattern );
 };
@@ -512,6 +528,8 @@ struct GradientFillModel
     void                readGradient( SequenceInputStream& rStrm );
     /** Reads BIFF12 gradient stop settings from a FILL or DXF record. */
     void                readGradientStop( SequenceInputStream& rStrm, bool 
bDxf );
+
+    bool operator==(const GradientFillModel&) const = default;
 };
 
 /** Contains API fill attributes. */
@@ -561,10 +579,14 @@ public:
 
     void                fillToItemSet( SfxItemSet& rItemSet, bool 
bSkipPoolDefs = false ) const;
 
-private:
     typedef std::shared_ptr< PatternFillModel >   PatternModelRef;
     typedef std::shared_ptr< GradientFillModel >  GradientModelRef;
 
+    const PatternModelRef & getPatternModel() const { return mxPatternModel; }
+    const GradientModelRef & getGradientModel() const {return mxGradientModel; 
}
+
+private:
+
     PatternModelRef     mxPatternModel;
     GradientModelRef    mxGradientModel;
     ApiSolidFillData    maApiData;
@@ -664,6 +686,7 @@ typedef std::shared_ptr< Xf > XfRef;
 
 class Dxf : public WorkbookHelper
 {
+friend struct CondFormatEquals;
 public:
     explicit            Dxf( const WorkbookHelper& rHelper );
 
diff --git a/sc/source/filter/oox/condformatbuffer.cxx 
b/sc/source/filter/oox/condformatbuffer.cxx
index d40b16a6e84c..59a613878507 100644
--- a/sc/source/filter/oox/condformatbuffer.cxx
+++ b/sc/source/filter/oox/condformatbuffer.cxx
@@ -1122,9 +1122,9 @@ void CondFormat::importConditionalFormatting( const 
AttributeList& rAttribs )
     mpFormat = new ScConditionalFormat(0, &getScDocument());
 }
 
-CondFormatRuleRef CondFormat::importCfRule( const AttributeList& rAttribs )
+std::unique_ptr<CondFormatRule> CondFormat::importCfRule( const AttributeList& 
rAttribs )
 {
-    CondFormatRuleRef xRule = createRule();
+    std::unique_ptr<CondFormatRule> xRule = createRule();
     xRule->importCfRule( rAttribs );
     return xRule;
 }
@@ -1140,9 +1140,9 @@ void CondFormat::importCondFormatting( 
SequenceInputStream& rStrm )
 
 void CondFormat::importCfRule( SequenceInputStream& rStrm )
 {
-    CondFormatRuleRef xRule = createRule();
+    std::unique_ptr<CondFormatRule> xRule = createRule();
     xRule->importCfRule( rStrm );
-    insertRule( xRule );
+    insertRule( std::move(xRule) );
 }
 
 void CondFormat::finalizeImport()
@@ -1152,7 +1152,8 @@ void CondFormat::finalizeImport()
         return;
     ScDocument& rDoc = getScDocument();
     mpFormat->SetRange(maModel.maRanges);
-    maRules.forEachMem( &CondFormatRule::finalizeImport );
+    for (auto & rPair : maRules)
+        rPair.second->finalizeImport();
 
     if (mpFormat->size() > 0)
     {
@@ -1164,17 +1165,18 @@ void CondFormat::finalizeImport()
     }
 }
 
-CondFormatRuleRef CondFormat::createRule()
+std::unique_ptr<CondFormatRule> CondFormat::createRule()
 {
-    return std::make_shared<CondFormatRule>( *this, mpFormat );
+    return std::make_unique<CondFormatRule>( *this, mpFormat );
 }
 
-void CondFormat::insertRule( CondFormatRuleRef const & xRule )
+void CondFormat::insertRule( std::unique_ptr<CondFormatRule> xRule )
 {
-    if( xRule && (xRule->getPriority() > 0) )
+    if( xRule->getPriority() > 0 )
     {
+        assert(&xRule->getParentCondFormat() == this);
         OSL_ENSURE( maRules.find( xRule->getPriority() ) == maRules.end(), 
"CondFormat::insertRule - multiple rules with equal priority" );
-        maRules[ xRule->getPriority() ] = xRule;
+        maRules[ xRule->getPriority() ] = std::move(xRule);
     }
 }
 
@@ -1229,34 +1231,10 @@ void CondFormatBuffer::updateImport(const 
ScDataBarFormatData* pTarget)
     }
 }
 
-bool CondFormatBuffer::insertRule(CondFormatRef const & xCondFmt, 
CondFormatRuleRef const & xRule)
-{
-    CondFormatRef xFoundFmt;
-    ScRangeList aRanges = xCondFmt->getRanges();
-
-    for (auto& rCondFmt : maCondFormats)
-    {
-        if (xCondFmt == rCondFmt)
-            continue;
-
-        if (aRanges == rCondFmt->getRanges())
-        {
-            xFoundFmt = rCondFmt;
-            break;
-        }
-    }
-
-    if (xFoundFmt)
-    {
-        xRule->mpFormat = xFoundFmt->mpFormat;
-        xFoundFmt->insertRule(xRule);
-    }
-
-    return static_cast<bool>(xFoundFmt);
-}
-
 void CondFormatBuffer::finalizeImport()
 {
+    deduplicateCondFormats();
+
     std::unordered_set<size_t> aDoneExtCFs;
     typedef std::unordered_map<ScRangeList, CondFormat*, ScRangeListHasher> 
RangeMap;
     RangeMap aRangeMap;
@@ -1281,7 +1259,7 @@ void CondFormatBuffer::finalizeImport()
             size_t nEntryIdx = 0;
             for (const auto& rxEntry : rEntries)
             {
-                CondFormatRuleRef xRule = rCondFormat.createRule();
+                std::unique_ptr<CondFormatRule> xRule = 
rCondFormat.createRule();
                 if (ScDataBarFormat *pData = 
dynamic_cast<ScDataBarFormat*>(rxEntry.get()))
                     updateImport(pData->GetDataBarData());
                 ScFormatEntry* pNewEntry = rxEntry->Clone(pDoc);
@@ -1289,7 +1267,7 @@ void CondFormatBuffer::finalizeImport()
                 if (nPriority == -1)
                     nPriority = mnNonPrioritizedRuleNextPriority++;
                 xRule->setFormatEntry(nPriority, pNewEntry);
-                rCondFormat.insertRule(xRule);
+                rCondFormat.insertRule(std::move(xRule));
                 ++nEntryIdx;
             }
 
@@ -1321,10 +1299,9 @@ void CondFormatBuffer::finalizeImport()
     }
 
     for( const auto& rxCondFormat : maCondFormats )
-    {
-        if ( rxCondFormat)
+        if (rxCondFormat)
             rxCondFormat->finalizeImport();
-    }
+
     for ( const auto& rxCfRule : maCfRules )
     {
         if ( rxCfRule )
@@ -1366,6 +1343,204 @@ void CondFormatBuffer::finalizeImport()
     gnStyleIdx = 0; // Resets <extlst> <cfRule> style index.
 }
 
+/// Used for deduplicating
+struct CondFormatHash
+{
+    size_t operator()(const CondFormatRef& x) const { return hashCode(*x); }
+
+private:
+    static size_t hashCode(const CondFormat& r)
+    {
+        std::size_t seed(0);
+        // note that we deliberately skip the maRanges field, because, if 
necessary, we will merge
+        // new entries into that field.
+        o3tl::hash_combine(seed, r.maModel.mbPivot);
+        for (const auto & rPair : r.maRules)
+            o3tl::hash_combine(seed, hashCode(*rPair.second));
+        return seed;
+    }
+    static size_t hashCode(const CondFormatRule& r)
+    {
+        std::size_t seed(0);
+        o3tl::hash_combine(seed, hashCode(r.getRuleModel()));
+        return seed;
+    }
+    static size_t hashCode(const CondFormatRuleModel& r)
+    {
+        std::size_t seed(0);
+        o3tl::hash_combine(seed, r.maText);
+        // we skip mnPriority, see comment in CondFormatEquals
+        o3tl::hash_combine(seed, r.mnType);
+        o3tl::hash_combine(seed, r.mnOperator);
+        o3tl::hash_combine(seed, r.mnTimePeriod);
+        o3tl::hash_combine(seed, r.mnRank);
+        o3tl::hash_combine(seed, r.mnStdDev);
+        // o3tl::hash_combine(seed, r.mnDxfId); if I hash this, need to hash 
the contents
+        o3tl::hash_combine(seed, r.mbStopIfTrue);
+        o3tl::hash_combine(seed, r.mbBottom);
+        o3tl::hash_combine(seed, r.mbPercent);
+        o3tl::hash_combine(seed, r.mbAboveAverage);
+        o3tl::hash_combine(seed, r.mbEqualAverage);
+        return seed;
+    }
+};
+
+/// Used for deduplicating
+struct CondFormatEquals
+{
+    const StylesBuffer& mrStyles;
+
+    CondFormatEquals(const StylesBuffer& rStyles) : mrStyles(rStyles) {}
+
+    bool operator()(const CondFormatRef& lhs, const CondFormatRef& rhs) const
+    {
+        if (lhs.get() == rhs.get())
+            return true;
+        // note that we deliberately skip the maRanges field, because, if 
necessary, we will merge
+        // new entries into that field.
+        if (lhs->maModel.mbPivot != rhs->maModel.mbPivot)
+            return false;
+        auto it1 = lhs->maRules.begin();
+        auto it2 = rhs->maRules.begin();
+        while (it1 != lhs->maRules.end() && it2 != rhs->maRules.end())
+        {
+            if (!equals(it1->second, it2->second))
+                return false;
+            ++it1;
+            ++it2;
+        }
+        return it1 == lhs->maRules.end() && it2 == rhs->maRules.end();
+    }
+private:
+    bool equals(const std::unique_ptr<CondFormatRule>& lhs, const 
std::unique_ptr<CondFormatRule>& rhs) const
+    {
+        if (lhs.get() == rhs.get())
+            return true;
+        if (!equals(lhs->getRuleModel(), rhs->getRuleModel()))
+            return false;
+        if (bool(lhs->mpColor) != bool(rhs->mpColor))
+            return false;
+        if (lhs->mpColor)
+        {
+            if (lhs->mpColor->getModelEntries() != 
rhs->mpColor->getModelEntries())
+                return false;
+            if (lhs->mpColor->getCfvo() != rhs->mpColor->getCfvo())
+                return false;
+            if (lhs->mpColor->getCol() != rhs->mpColor->getCol())
+                return false;
+        }
+        // todo: I'm not bothering to properly check the following fields
+        // because I ran out of enthusiasm, so we wont detect duplicates if 
they
+        // contain data here.
+        if (lhs->mpDataBar.get() != rhs->mpDataBar.get())
+            return false;
+        if (lhs->mpIconSet.get() != rhs->mpIconSet.get())
+            return false;
+        return true;
+    }
+    bool equals(const CondFormatRuleModel& lhs, const CondFormatRuleModel& 
rhs) const
+    {
+        if (lhs.maText != rhs.maText)
+            return false;
+        // we skip mnPriority, because that is implicitly compared by the 
ordering of these objects
+        if (lhs.mnType != rhs.mnType)
+            return false;
+        if (lhs.mnOperator != rhs.mnOperator)
+            return false;
+        if (lhs.mnTimePeriod != rhs.mnTimePeriod)
+            return false;
+        if (lhs.mnRank != rhs.mnRank)
+            return false;
+        if (lhs.mnStdDev != rhs.mnStdDev)
+            return false;
+        if (!equalsDxf(lhs.mnDxfId, rhs.mnDxfId))
+            return false;
+        if (lhs.mbStopIfTrue != rhs.mbStopIfTrue)
+            return false;
+        if (lhs.mbBottom != rhs.mbBottom)
+            return false;
+        if (lhs.mbPercent != rhs.mbPercent)
+            return false;
+        if (lhs.mbAboveAverage != rhs.mbAboveAverage)
+            return false;
+        if (lhs.mbEqualAverage != rhs.mbEqualAverage)
+            return false;
+        return true;
+    }
+    bool equalsDxf(sal_Int32 lhsDxfId, sal_Int32 rhsDxfId) const
+    {
+        if (lhsDxfId == rhsDxfId)
+            return true;
+        DxfRef pLhsDxf = mrStyles.getDxf(lhsDxfId);
+        DxfRef pRhsDxf = mrStyles.getDxf(rhsDxfId);
+        if (bool(pLhsDxf) != bool(pRhsDxf))
+            return false;
+        if (pLhsDxf && !equals(*pLhsDxf, *pRhsDxf))
+            return false;
+        return true;
+    }
+    static bool equals(const Dxf& lhs, const Dxf& rhs)
+    {
+        if (bool(lhs.mxFont) != bool(rhs.mxFont))
+            return false;
+        if (lhs.mxFont && lhs.mxFont->getModel() != rhs.mxFont->getModel())
+            return false;
+        if (bool(lhs.mxNumFmt) != bool(rhs.mxNumFmt))
+            return false;
+        if (lhs.mxNumFmt && lhs.mxNumFmt->getModel() != 
rhs.mxNumFmt->getModel())
+            return false;
+        if (bool(lhs.mxAlignment) != bool(rhs.mxAlignment))
+            return false;
+        if (lhs.mxAlignment && lhs.mxAlignment->getModel() != 
rhs.mxAlignment->getModel())
+            return false;
+        if (bool(lhs.mxProtection) != bool(rhs.mxProtection))
+            return false;
+        if (lhs.mxProtection && lhs.mxProtection->getModelData() != 
rhs.mxProtection->getModelData())
+            return false;
+        if (bool(lhs.mxBorder) != bool(rhs.mxBorder))
+            return false;
+        if (lhs.mxBorder && lhs.mxBorder->getModelData() != 
rhs.mxBorder->getModelData())
+            return false;
+        if (bool(lhs.mxFill) != bool(rhs.mxFill))
+            return false;
+        if (lhs.mxFill)
+        {
+            if (bool(lhs.mxFill->getPatternModel()) != 
bool(rhs.mxFill->getPatternModel()))
+                return false;
+            if (lhs.mxFill->getPatternModel() && 
*lhs.mxFill->getPatternModel() != *rhs.mxFill->getPatternModel())
+                return false;
+            if (bool(lhs.mxFill->getGradientModel()) != 
bool(rhs.mxFill->getGradientModel()))
+                return false;
+            if (lhs.mxFill->getGradientModel() && 
*lhs.mxFill->getGradientModel() != *rhs.mxFill->getGradientModel())
+                return false;
+        }
+        return true;
+    }
+};
+
+// Excel will sometimes produce files with 100k of duplicate conditional 
formatting
+// entries, but only 10 unique ones, which will make LO freeze because our 
ScPatternAttr
+// de-duplication is quite expensive. So rather do it here, where it is 
considerably cheaper.
+void CondFormatBuffer::deduplicateCondFormats()
+{
+    // remove duplicates as we go
+    std::unordered_set<CondFormatRef, CondFormatHash, CondFormatEquals> 
aDeduped{5, CondFormatHash(), CondFormatEquals{getStyles()}};
+    for( auto it = maCondFormats.begin(); it != maCondFormats.end(); )
+    {
+        auto pair = aDeduped.insert(*it);
+        // If the insert did not succeed, we have a duplicate, and we need to 
merge the ranges of this item
+        // into the existing ranges list.
+        if (!pair.second)
+        {
+            for (const auto & rRange : (*it)->maModel.maRanges)
+                (*pair.first)->maModel.maRanges.push_back(rRange);
+            it = maCondFormats.erase(it);
+        }
+        else
+            ++it;
+    }
+}
+
 CondFormatRef CondFormatBuffer::importCondFormatting( SequenceInputStream& 
rStrm )
 {
     CondFormatRef xCondFmt = createCondFormat();
diff --git a/sc/source/filter/oox/condformatcontext.cxx 
b/sc/source/filter/oox/condformatcontext.cxx
index 5da1f721ffc6..6f3f988d8abe 100644
--- a/sc/source/filter/oox/condformatcontext.cxx
+++ b/sc/source/filter/oox/condformatcontext.cxx
@@ -29,9 +29,9 @@ namespace oox::xls {
 
 using ::oox::core::ContextHandlerRef;
 
-ColorScaleContext::ColorScaleContext( CondFormatContext& rFragment, 
CondFormatRuleRef xRule ) :
+ColorScaleContext::ColorScaleContext( CondFormatContext& rFragment, 
CondFormatRule& rRule ) :
     WorksheetContextBase( rFragment ),
-    mxRule(std::move( xRule ))
+    mrRule( rRule )
 {
 }
 
@@ -57,17 +57,17 @@ void ColorScaleContext::onStartElement( const 
AttributeList& rAttribs )
     switch( getCurrentElement() )
     {
         case XLS_TOKEN( cfvo ):
-            mxRule->getColorScale()->importCfvo( rAttribs );
+            mrRule.getColorScale()->importCfvo( rAttribs );
         break;
         case XLS_TOKEN( color ):
-            mxRule->getColorScale()->importColor( rAttribs );
+            mrRule.getColorScale()->importColor( rAttribs );
         break;
     }
 }
 
-DataBarContext::DataBarContext( CondFormatContext& rFragment, 
CondFormatRuleRef xRule ) :
+DataBarContext::DataBarContext( CondFormatContext& rFragment, CondFormatRule& 
rRule ) :
     WorksheetContextBase( rFragment ),
-    mxRule(std::move( xRule ))
+    mrRule( rRule )
 {
 }
 
@@ -93,13 +93,13 @@ void DataBarContext::onStartElement( const AttributeList& 
rAttribs )
     switch( getCurrentElement() )
     {
         case XLS_TOKEN( dataBar ):
-            mxRule->getDataBar()->importAttribs( rAttribs );
+            mrRule.getDataBar()->importAttribs( rAttribs );
         break;
         case XLS_TOKEN( cfvo ):
-            mxRule->getDataBar()->importCfvo( rAttribs );
+            mrRule.getDataBar()->importCfvo( rAttribs );
         break;
         case XLS_TOKEN( color ):
-            mxRule->getDataBar()->importColor( rAttribs );
+            mrRule.getDataBar()->importColor( rAttribs );
         break;
     }
 }
@@ -181,9 +181,9 @@ ContextHandlerRef CondFormatContext::onCreateContext( 
sal_Int32 nElement, const
             if (nElement == XLS_TOKEN( formula ))
                 return this;
             else if (nElement == XLS_TOKEN( colorScale ) )
-                return new ColorScaleContext( *this, mxRule );
+                return new ColorScaleContext( *this, *mxRule );
             else if (nElement == XLS_TOKEN( dataBar ) )
-                return new DataBarContext( *this, mxRule );
+                return new DataBarContext( *this, *mxRule );
             else if (nElement == XLS_TOKEN( iconSet ) )
                 return new IconSetContext(*this, mxRule->getIconSet());
             else if (nElement == XLS_TOKEN( extLst ) )
@@ -204,12 +204,7 @@ void CondFormatContext::onEndElement()
             break;
         case XLS_TOKEN( cfRule ):
             if (mxCondFmt && mxRule)
-            {
-                ScRangeList aRanges = mxCondFmt->getRanges();
-                if ((aRanges.size() == 1 && aRanges.GetCellCount() == 1) ||
-                    !getCondFormats().insertRule(mxCondFmt, mxRule))
-                    mxCondFmt->insertRule(mxRule);
-            }
+                mxCondFmt->insertRule(std::move(mxRule));
         break;
     }
 }
diff --git a/sc/source/filter/oox/stylesbuffer.cxx 
b/sc/source/filter/oox/stylesbuffer.cxx
index ab0af4cd2149..c3af8de44034 100644
--- a/sc/source/filter/oox/stylesbuffer.cxx
+++ b/sc/source/filter/oox/stylesbuffer.cxx
@@ -500,6 +500,37 @@ void FontModel::setBiffEscapement( sal_uInt16 nEscapement )
     mnEscapement = STATIC_ARRAY_SELECT( spnEscapes, nEscapement, XML_baseline 
);
 }
 
+bool FontModel::operator==(const FontModel& rhs) const
+{
+    if (maName != rhs.maName)
+        return false;
+    if (maColor != rhs.maColor)
+        return false;
+    if (mnScheme != rhs.mnScheme)
+        return false;
+    if (mnFamily != rhs.mnFamily)
+        return false;
+    if (mnCharSet != rhs.mnCharSet)
+        return false;
+    if (mfHeight != rhs.mfHeight)
+        return false;
+    if (mnUnderline != rhs.mnUnderline)
+        return false;
+    if (mnEscapement != rhs.mnEscapement)
+        return false;
+    if (mbBold != rhs.mbBold)
+        return false;
+    if (mbItalic != rhs.mbItalic)
+        return false;
+    if (mbStrikeout != rhs.mbStrikeout)
+        return false;
+    if (mbOutline != rhs.mbOutline)
+        return false;
+    if (mbShadow != rhs.mbShadow)
+        return false;
+    return true;
+}
+
 ApiFontUsedFlags::ApiFontUsedFlags( bool bAllUsed ) :
     mbNameUsed( bAllUsed ),
     mbColorUsed( bAllUsed ),
@@ -1442,6 +1473,17 @@ void BorderLineModel::setBiffStyle( sal_Int32 nLineStyle 
)
     mnStyle = STATIC_ARRAY_SELECT( spnStyleIds, nLineStyle, XML_none );
 }
 
+bool BorderLineModel::operator==(const BorderLineModel& rhs) const
+{
+    if (maColor != rhs.maColor)
+        return false;
+    if (mnStyle != rhs.mnStyle)
+        return false;
+    if (mbUsed != rhs.mbUsed)
+        return false;
+    return true;
+}
+
 BorderModel::BorderModel( bool bDxf ) :
     maLeft( bDxf ),
     maRight( bDxf ),
@@ -1699,6 +1741,25 @@ void PatternFillModel::setBiffPattern( sal_Int32 
nPattern )
     mnPattern = STATIC_ARRAY_SELECT( spnPatternIds, nPattern, XML_none );
 }
 
+bool PatternFillModel::operator==(const PatternFillModel& rhs) const
+{
+    if (maPatternColor != rhs.maPatternColor)
+        return false;
+    if (maFilterPatternColor != rhs.maFilterPatternColor)
+        return false;
+    if (maFillColor != rhs.maFillColor)
+        return false;
+    if (mnPattern != rhs.mnPattern)
+        return false;
+    if (mbPattColorUsed != rhs.mbPattColorUsed)
+        return false;
+    if (mbFillColorUsed != rhs.mbFillColorUsed)
+        return false;
+    if (mbPatternUsed != rhs.mbPatternUsed)
+        return false;
+    return true;
+}
+
 GradientFillModel::GradientFillModel() :
     mnType( XML_linear ),
     mfAngle( 0.0 ),

Reply via email to