1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
3 * This file is part of the LibreOffice project.
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
9 * This file incorporates work covered by the following license notice:
11 * Licensed to the Apache Software Foundation (ASF) under one or more
12 * contributor license agreements. See the NOTICE file distributed
13 * with this work for additional information regarding copyright
14 * ownership. The ASF licenses this file to you under the Apache
15 * License, Version 2.0 (the "License"); you may not use this file
16 * except in compliance with the License. You may obtain a copy of
17 * the License at http://www.apache.org/licenses/LICENSE-2.0 .
20 #include <autofilterbuffer.hxx>
22 #include <com/sun/star/beans/XPropertySet.hpp>
23 #include <com/sun/star/sheet/FilterFieldType.hpp>
24 #include <com/sun/star/sheet/FilterConnection.hpp>
25 #include <com/sun/star/sheet/FilterOperator2.hpp>
26 #include <com/sun/star/sheet/TableFilterField3.hpp>
27 #include <com/sun/star/sheet/XDatabaseRange.hpp>
28 #include <com/sun/star/sheet/XSheetFilterDescriptor3.hpp>
29 #include <com/sun/star/table/TableOrientation.hpp>
30 #include <com/sun/star/table/CellAddress.hpp>
32 #include <comphelper/sequence.hxx>
33 #include <editeng/brushitem.hxx>
34 #include <rtl/ustrbuf.hxx>
35 #include <osl/diagnose.h>
36 #include <oox/helper/attributelist.hxx>
37 #include <oox/helper/propertyset.hxx>
38 #include <oox/helper/binaryinputstream.hxx>
39 #include <oox/token/namespaces.hxx>
40 #include <oox/token/properties.hxx>
41 #include <oox/token/tokens.hxx>
42 #include <addressconverter.hxx>
43 #include <defnamesbuffer.hxx>
44 #include <biffhelper.hxx>
45 #include <document.hxx>
47 #include <scitems.hxx>
48 #include <sortparam.hxx>
49 #include <stlpool.hxx>
50 #include <stlsheet.hxx>
51 #include <stylesbuffer.hxx>
52 #include <userlist.hxx>
57 using namespace ::com::sun::star::sheet
;
58 using namespace ::com::sun::star::table
;
59 using namespace ::com::sun::star::uno
;
63 const sal_uInt8 BIFF12_TOP10FILTER_TOP
= 0x01;
64 const sal_uInt8 BIFF12_TOP10FILTER_PERCENT
= 0x02;
66 const sal_uInt16 BIFF12_FILTERCOLUMN_HIDDENBUTTON
= 0x0001;
67 const sal_uInt16 BIFF12_FILTERCOLUMN_SHOWBUTTON
= 0x0002;
69 const sal_uInt8 BIFF_FILTER_DATATYPE_NONE
= 0;
70 const sal_uInt8 BIFF_FILTER_DATATYPE_DOUBLE
= 4;
71 const sal_uInt8 BIFF_FILTER_DATATYPE_STRING
= 6;
72 const sal_uInt8 BIFF_FILTER_DATATYPE_BOOLEAN
= 8;
73 const sal_uInt8 BIFF_FILTER_DATATYPE_EMPTY
= 12;
74 const sal_uInt8 BIFF_FILTER_DATATYPE_NOTEMPTY
= 14;
76 bool lclGetApiOperatorFromToken( sal_Int32
& rnApiOperator
, sal_Int32 nToken
)
80 case XML_lessThan
: rnApiOperator
= FilterOperator2::LESS
; return true;
81 case XML_equal
: rnApiOperator
= FilterOperator2::EQUAL
; return true;
82 case XML_lessThanOrEqual
: rnApiOperator
= FilterOperator2::LESS_EQUAL
; return true;
83 case XML_greaterThan
: rnApiOperator
= FilterOperator2::GREATER
; return true;
84 case XML_notEqual
: rnApiOperator
= FilterOperator2::NOT_EQUAL
; return true;
85 case XML_greaterThanOrEqual
: rnApiOperator
= FilterOperator2::GREATER_EQUAL
; return true;
90 /** Removes leading asterisk characters from the passed string.
91 @return True = at least one asterisk character has been removed. */
92 bool lclTrimLeadingAsterisks( OUString
& rValue
)
94 sal_Int32 nLength
= rValue
.getLength();
96 while( (nPos
< nLength
) && (rValue
[ nPos
] == '*') )
100 rValue
= rValue
.copy( nPos
);
106 /** Removes trailing asterisk characters from the passed string.
107 @return True = at least one asterisk character has been removed. */
108 bool lclTrimTrailingAsterisks( OUString
& rValue
)
110 sal_Int32 nLength
= rValue
.getLength();
111 sal_Int32 nPos
= nLength
;
112 while( (nPos
> 0) && (rValue
[ nPos
- 1 ] == '*') )
116 rValue
= rValue
.copy( 0, nPos
);
122 /** Converts wildcard characters '*' and '?' to regular expressions and quotes
124 @return True = passed string has been changed (RE needs to be enabled). */
125 bool lclConvertWildcardsToRegExp( OUString
& rValue
)
127 // check existence of the wildcard characters '*' and '?'
128 if( !rValue
.isEmpty() && ((rValue
.indexOf( '*' ) >= 0) || (rValue
.indexOf( '?' ) >= 0)) )
130 OUStringBuffer aBuffer
;
131 aBuffer
.ensureCapacity( rValue
.getLength() + 5 );
132 const sal_Unicode
* pcChar
= rValue
.getStr();
133 const sal_Unicode
* pcEnd
= pcChar
+ rValue
.getLength();
134 for( ; pcChar
< pcEnd
; ++pcChar
)
139 aBuffer
.append( '.' );
142 aBuffer
.append( ".*" );
144 case '\\': case '.': case '|': case '(': case ')': case '^': case '$':
145 // quote RE meta characters
146 aBuffer
.append( "\\" + OUStringChar(*pcChar
) );
149 aBuffer
.append( *pcChar
);
152 rValue
= aBuffer
.makeStringAndClear();
160 ApiFilterSettings::ApiFilterSettings()
164 void ApiFilterSettings::appendField( bool bAnd
, sal_Int32 nOperator
, double fValue
)
166 maFilterFields
.emplace_back();
167 TableFilterField3
& rFilterField
= maFilterFields
.back();
168 rFilterField
.Connection
= bAnd
? FilterConnection_AND
: FilterConnection_OR
;
169 rFilterField
.Operator
= nOperator
;
170 rFilterField
.Values
.realloc(1);
171 auto pValues
= rFilterField
.Values
.getArray();
172 pValues
[0].FilterType
= FilterFieldType::NUMERIC
;
173 pValues
[0].NumericValue
= fValue
;
176 void ApiFilterSettings::appendField( bool bAnd
, sal_Int32 nOperator
, const OUString
& rValue
)
178 maFilterFields
.emplace_back();
179 TableFilterField3
& rFilterField
= maFilterFields
.back();
180 rFilterField
.Connection
= bAnd
? FilterConnection_AND
: FilterConnection_OR
;
181 rFilterField
.Operator
= nOperator
;
182 rFilterField
.Values
.realloc(1);
183 auto pValues
= rFilterField
.Values
.getArray();
184 pValues
[0].FilterType
= FilterFieldType::STRING
;
185 pValues
[0].StringValue
= rValue
;
188 void ApiFilterSettings::appendField(bool bAnd
, util::Color aColor
, bool bIsBackgroundColor
)
190 maFilterFields
.emplace_back();
191 TableFilterField3
& rFilterField
= maFilterFields
.back();
192 rFilterField
.Connection
= bAnd
? FilterConnection_AND
: FilterConnection_OR
;
193 rFilterField
.Operator
= FilterOperator2::EQUAL
;
194 rFilterField
.Values
.realloc(1);
195 auto pValues
= rFilterField
.Values
.getArray();
196 pValues
[0].FilterType
197 = bIsBackgroundColor
? FilterFieldType::BACKGROUND_COLOR
: FilterFieldType::TEXT_COLOR
;
198 pValues
[0].ColorValue
= aColor
;
201 void ApiFilterSettings::appendField( bool bAnd
, const std::vector
<std::pair
<OUString
, bool>>& rValues
)
203 maFilterFields
.emplace_back();
204 TableFilterField3
& rFilterField
= maFilterFields
.back();
205 rFilterField
.Connection
= bAnd
? FilterConnection_AND
: FilterConnection_OR
;
206 rFilterField
.Operator
= FilterOperator2::EQUAL
;
207 rFilterField
.Values
.realloc(rValues
.size());
208 auto pValues
= rFilterField
.Values
.getArray();
211 for( auto const& it
: rValues
)
213 pValues
[i
].StringValue
= it
.first
;
214 pValues
[i
++].FilterType
215 = it
.second
? FilterFieldType::DATE
: FilterFieldType::STRING
;
219 FilterSettingsBase::FilterSettingsBase( const WorkbookHelper
& rHelper
) :
220 WorkbookHelper( rHelper
)
224 void FilterSettingsBase::importAttribs( sal_Int32
/*nElement*/, const AttributeList
& /*rAttribs*/ )
228 void FilterSettingsBase::importRecord( sal_Int32
/*nRecId*/, SequenceInputStream
& /*rStrm*/ )
232 ApiFilterSettings
FilterSettingsBase::finalizeImport()
234 return ApiFilterSettings();
237 DiscreteFilter::DiscreteFilter( const WorkbookHelper
& rHelper
) :
238 FilterSettingsBase( rHelper
),
239 mnCalendarType( XML_none
),
244 void DiscreteFilter::importAttribs( sal_Int32 nElement
, const AttributeList
& rAttribs
)
248 case XLS_TOKEN( filters
):
249 mnCalendarType
= rAttribs
.getToken( XML_calendarType
, XML_none
);
250 mbShowBlank
= rAttribs
.getBool( XML_blank
, false );
253 case XLS_TOKEN( filter
):
255 OUString aValue
= rAttribs
.getXString( XML_val
, OUString() );
256 if( !aValue
.isEmpty() )
257 maValues
.push_back( std::make_pair(aValue
, false) );
261 case XLS_TOKEN( dateGroupItem
):
264 // it is just a fallback, we do not need the XML_day as default value,
265 // because if the dateGroupItem exists also XML_dateTimeGrouping exists!
266 sal_uInt16 nToken
= rAttribs
.getToken(XML_dateTimeGrouping
, XML_day
);
267 if( nToken
== XML_year
|| nToken
== XML_month
|| nToken
== XML_day
||
268 nToken
== XML_hour
|| nToken
== XML_minute
|| nToken
== XML_second
)
270 aDateValue
= rAttribs
.getString(XML_year
, OUString());
272 if( nToken
== XML_month
|| nToken
== XML_day
|| nToken
== XML_hour
||
273 nToken
== XML_minute
|| nToken
== XML_second
)
275 OUString aMonthName
= rAttribs
.getString(XML_month
, OUString());
276 if( aMonthName
.getLength() == 1 )
277 aMonthName
= "0" + aMonthName
;
278 aDateValue
+= "-" + aMonthName
;
280 if( nToken
== XML_day
|| nToken
== XML_hour
|| nToken
== XML_minute
||
281 nToken
== XML_second
)
283 OUString aDayName
= rAttribs
.getString(XML_day
, OUString());
284 if( aDayName
.getLength() == 1 )
285 aDayName
= "0" + aDayName
;
286 aDateValue
+= "-" + aDayName
;
288 if( nToken
== XML_hour
|| nToken
== XML_minute
|| nToken
== XML_second
)
290 OUString aHourName
= rAttribs
.getString(XML_hour
, OUString());
291 if( aHourName
.getLength() == 1 )
292 aHourName
= "0" + aHourName
;
293 aDateValue
+= " " + aHourName
;
295 if( nToken
== XML_minute
|| nToken
== XML_second
)
297 OUString aMinName
= rAttribs
.getString(XML_minute
, OUString());
298 if( aMinName
.getLength() == 1 )
299 aMinName
= "0" + aMinName
;
300 aDateValue
+= ":" + aMinName
;
302 if( nToken
== XML_second
)
304 OUString aSecName
= rAttribs
.getString(XML_second
, OUString());
305 if( aSecName
.getLength() == 1 )
306 aSecName
= "0" + aSecName
;
307 aDateValue
+= ":" + aSecName
;
314 if( !aDateValue
.isEmpty() )
315 maValues
.push_back( std::make_pair(aDateValue
, true) );
321 void DiscreteFilter::importRecord( sal_Int32 nRecId
, SequenceInputStream
& rStrm
)
325 case BIFF12_ID_DISCRETEFILTERS
:
327 sal_Int32 nShowBlank
, nCalendarType
;
328 nShowBlank
= rStrm
.readInt32();
329 nCalendarType
= rStrm
.readInt32();
331 static const sal_Int32 spnCalendarTypes
[] = {
332 XML_none
, XML_gregorian
, XML_gregorianUs
, XML_japan
, XML_taiwan
, XML_korea
, XML_hijri
, XML_thai
, XML_hebrew
,
333 XML_gregorianMeFrench
, XML_gregorianArabic
, XML_gregorianXlitEnglish
, XML_gregorianXlitFrench
};
334 mnCalendarType
= STATIC_ARRAY_SELECT( spnCalendarTypes
, nCalendarType
, XML_none
);
335 mbShowBlank
= nShowBlank
!= 0;
339 case BIFF12_ID_DISCRETEFILTER
:
341 OUString aValue
= BiffHelper::readString( rStrm
);
342 if( !aValue
.isEmpty() )
343 maValues
.push_back( std::make_pair(aValue
, false) );
349 ApiFilterSettings
DiscreteFilter::finalizeImport()
351 ApiFilterSettings aSettings
;
352 aSettings
.maFilterFields
.reserve( maValues
.size() );
354 // insert all filter values
355 aSettings
.appendField( true, maValues
);
357 // extra field for 'show empty'
359 aSettings
.appendField( false, FilterOperator2::EMPTY
, OUString() );
361 /* Require disabled regular expressions, filter entries may contain
362 any RE meta characters. */
363 if( !maValues
.empty() )
364 aSettings
.mobNeedsRegExp
= false;
369 Top10Filter::Top10Filter( const WorkbookHelper
& rHelper
) :
370 FilterSettingsBase( rHelper
),
377 void Top10Filter::importAttribs( sal_Int32 nElement
, const AttributeList
& rAttribs
)
379 if( nElement
== XLS_TOKEN( top10
) )
381 mfValue
= rAttribs
.getDouble( XML_val
, 0.0 );
382 mbTop
= rAttribs
.getBool( XML_top
, true );
383 mbPercent
= rAttribs
.getBool( XML_percent
, false );
387 void Top10Filter::importRecord( sal_Int32 nRecId
, SequenceInputStream
& rStrm
)
389 if( nRecId
== BIFF12_ID_TOP10FILTER
)
392 nFlags
= rStrm
.readuChar();
393 mfValue
= rStrm
.readDouble();
394 mbTop
= getFlag( nFlags
, BIFF12_TOP10FILTER_TOP
);
395 mbPercent
= getFlag( nFlags
, BIFF12_TOP10FILTER_PERCENT
);
399 ApiFilterSettings
Top10Filter::finalizeImport()
401 sal_Int32 nOperator
= mbTop
?
402 (mbPercent
? FilterOperator2::TOP_PERCENT
: FilterOperator2::TOP_VALUES
) :
403 (mbPercent
? FilterOperator2::BOTTOM_PERCENT
: FilterOperator2::BOTTOM_VALUES
);
404 ApiFilterSettings aSettings
;
405 aSettings
.appendField( true, nOperator
, mfValue
);
409 ColorFilter::ColorFilter(const WorkbookHelper
& rHelper
)
410 : FilterSettingsBase(rHelper
)
411 , mbIsBackgroundColor(false)
415 void ColorFilter::importAttribs(sal_Int32 nElement
, const AttributeList
& rAttribs
)
417 if (nElement
== XLS_TOKEN(colorFilter
))
419 // When cellColor attribute not found, it means cellColor = true
420 // cellColor = 0 (false) -> TextColor
421 // cellColor = 1 (true) -> BackgroundColor
422 mbIsBackgroundColor
= rAttribs
.getBool(XML_cellColor
, true);
423 msStyleName
= getStyles().createDxfStyle( rAttribs
.getInteger(XML_dxfId
, -1) );
427 void ColorFilter::importRecord(sal_Int32
/* nRecId */, SequenceInputStream
& /* rStrm */)
432 ApiFilterSettings
ColorFilter::finalizeImport()
434 ApiFilterSettings aSettings
;
435 ScDocument
& rDoc
= getScDocument();
436 ScStyleSheet
* pStyleSheet
= static_cast<ScStyleSheet
*>(
437 rDoc
.GetStyleSheetPool()->Find(msStyleName
, SfxStyleFamily::Para
));
441 const SfxItemSet
& rItemSet
= pStyleSheet
->GetItemSet();
442 // Color (whether text or background color) is always stored in ATTR_BACKGROUND
443 if (const SvxBrushItem
* pItem
= rItemSet
.GetItem
<SvxBrushItem
>(ATTR_BACKGROUND
))
445 ::Color aColor
= pItem
->GetFiltColor();
446 util::Color
nColor(aColor
);
447 aSettings
.appendField(true, nColor
, mbIsBackgroundColor
);
452 FilterCriterionModel::FilterCriterionModel() :
453 mnOperator( XML_equal
),
454 mnDataType( BIFF_FILTER_DATATYPE_NONE
)
458 void FilterCriterionModel::setBiffOperator( sal_uInt8 nOperator
)
460 static const sal_Int32 spnOperators
[] = { XML_TOKEN_INVALID
,
461 XML_lessThan
, XML_equal
, XML_lessThanOrEqual
, XML_greaterThan
, XML_notEqual
, XML_greaterThanOrEqual
};
462 mnOperator
= STATIC_ARRAY_SELECT( spnOperators
, nOperator
, XML_TOKEN_INVALID
);
465 void FilterCriterionModel::readBiffData( SequenceInputStream
& rStrm
)
468 mnDataType
= rStrm
.readuChar();
469 nOperator
= rStrm
.readuChar();
470 setBiffOperator( nOperator
);
474 case BIFF_FILTER_DATATYPE_DOUBLE
:
475 maValue
<<= rStrm
.readDouble();
477 case BIFF_FILTER_DATATYPE_STRING
:
480 OUString aValue
= BiffHelper::readString( rStrm
).trim();
481 if( !aValue
.isEmpty() )
485 case BIFF_FILTER_DATATYPE_BOOLEAN
:
486 maValue
<<= (rStrm
.readuInt8() != 0);
489 case BIFF_FILTER_DATATYPE_EMPTY
:
491 if( mnOperator
== XML_equal
)
492 maValue
<<= OUString();
494 case BIFF_FILTER_DATATYPE_NOTEMPTY
:
496 if( mnOperator
== XML_notEqual
)
497 maValue
<<= OUString();
500 OSL_ENSURE( false, "FilterCriterionModel::readBiffData - unexpected data type" );
505 CustomFilter::CustomFilter( const WorkbookHelper
& rHelper
) :
506 FilterSettingsBase( rHelper
),
511 void CustomFilter::importAttribs( sal_Int32 nElement
, const AttributeList
& rAttribs
)
515 case XLS_TOKEN( customFilters
):
516 mbAnd
= rAttribs
.getBool( XML_and
, false );
519 case XLS_TOKEN( customFilter
):
521 FilterCriterionModel aCriterion
;
522 aCriterion
.mnOperator
= rAttribs
.getToken( XML_operator
, XML_equal
);
523 OUString aValue
= rAttribs
.getXString( XML_val
, OUString() ).trim();
524 if( (aCriterion
.mnOperator
== XML_equal
) || (aCriterion
.mnOperator
== XML_notEqual
) || (!aValue
.isEmpty()) )
525 aCriterion
.maValue
<<= aValue
;
526 appendCriterion( aCriterion
);
532 void CustomFilter::importRecord( sal_Int32 nRecId
, SequenceInputStream
& rStrm
)
536 case BIFF12_ID_CUSTOMFILTERS
:
537 mbAnd
= rStrm
.readInt32() == 0;
540 case BIFF12_ID_CUSTOMFILTER
:
542 FilterCriterionModel aCriterion
;
543 aCriterion
.readBiffData( rStrm
);
544 appendCriterion( aCriterion
);
550 ApiFilterSettings
CustomFilter::finalizeImport()
552 ApiFilterSettings aSettings
;
553 OSL_ENSURE( maCriteria
.size() <= 2, "CustomFilter::finalizeImport - too many filter criteria" );
554 for( const auto& rCriterion
: maCriteria
)
556 // first extract the filter operator
557 sal_Int32 nOperator
= 0;
558 bool bValidOperator
= lclGetApiOperatorFromToken( nOperator
, rCriterion
.mnOperator
);
561 if( rCriterion
.maValue
.has
< OUString
>() )
565 rCriterion
.maValue
>>= aValue
;
566 // check for 'empty', 'contains', 'begins with', or 'ends with' text filters
567 bool bEqual
= nOperator
== FilterOperator2::EQUAL
;
568 bool bNotEqual
= nOperator
== FilterOperator2::NOT_EQUAL
;
569 if( bEqual
|| bNotEqual
)
571 if( aValue
.isEmpty() )
573 // empty comparison string: create empty/not empty filters
574 nOperator
= bNotEqual
? FilterOperator2::NOT_EMPTY
: FilterOperator2::EMPTY
;
578 // compare to something: try to find begins/ends/contains
579 bool bHasLeadingAsterisk
= lclTrimLeadingAsterisks( aValue
);
580 bool bHasTrailingAsterisk
= lclTrimTrailingAsterisks( aValue
);
581 // just '***' matches everything, do not create a filter field
582 bValidOperator
= !aValue
.isEmpty();
585 if( bHasLeadingAsterisk
&& bHasTrailingAsterisk
)
586 nOperator
= bNotEqual
? FilterOperator2::DOES_NOT_CONTAIN
: FilterOperator2::CONTAINS
;
587 else if( bHasLeadingAsterisk
)
588 nOperator
= bNotEqual
? FilterOperator2::DOES_NOT_END_WITH
: FilterOperator2::ENDS_WITH
;
589 else if( bHasTrailingAsterisk
)
590 nOperator
= bNotEqual
? FilterOperator2::DOES_NOT_BEGIN_WITH
: FilterOperator2::BEGINS_WITH
;
591 // else: no asterisks, stick to equal/not equal
598 // if wildcards are present, require RE mode, otherwise keep don't care state
599 if( lclConvertWildcardsToRegExp( aValue
) )
600 aSettings
.mobNeedsRegExp
= true;
601 // create a new UNO API filter field
602 aSettings
.appendField( mbAnd
, nOperator
, aValue
);
605 else if( rCriterion
.maValue
.has
< double >() )
607 // floating-point argument
609 rCriterion
.maValue
>>= fValue
;
610 aSettings
.appendField( mbAnd
, nOperator
, fValue
);
617 void CustomFilter::appendCriterion( const FilterCriterionModel
& rCriterion
)
619 if( (rCriterion
.mnOperator
!= XML_TOKEN_INVALID
) && rCriterion
.maValue
.hasValue() )
620 maCriteria
.push_back( rCriterion
);
623 FilterColumn::FilterColumn( const WorkbookHelper
& rHelper
) :
624 WorkbookHelper( rHelper
),
626 mbHiddenButton( false ),
631 void FilterColumn::importFilterColumn( const AttributeList
& rAttribs
)
633 mnColId
= rAttribs
.getInteger( XML_colId
, -1 );
634 mbHiddenButton
= rAttribs
.getBool( XML_hiddenButton
, false );
635 mbShowButton
= rAttribs
.getBool( XML_showButton
, true );
638 void FilterColumn::importFilterColumn( SequenceInputStream
& rStrm
)
641 mnColId
= rStrm
.readInt32();
642 nFlags
= rStrm
.readuInt16();
643 mbHiddenButton
= getFlag( nFlags
, BIFF12_FILTERCOLUMN_HIDDENBUTTON
);
644 mbShowButton
= getFlag( nFlags
, BIFF12_FILTERCOLUMN_SHOWBUTTON
);
647 ApiFilterSettings
FilterColumn::finalizeImport()
649 ApiFilterSettings aSettings
;
650 if( (0 <= mnColId
) && mxSettings
)
652 // filter settings object creates a sequence of filter fields
653 aSettings
= mxSettings
->finalizeImport();
654 // add column index to all filter fields
655 for( auto& rFilterField
: aSettings
.maFilterFields
)
656 rFilterField
.Field
= mnColId
;
661 bool FilterColumn::isButtonHidden()
663 return (mbShowButton
== false) || (mbHiddenButton
== true);
668 SortCondition::SortCondition( const WorkbookHelper
& rHelper
) :
669 WorkbookHelper( rHelper
),
670 mbDescending( false )
674 void SortCondition::importSortCondition( const AttributeList
& rAttribs
, sal_Int16 nSheet
)
676 OUString aRangeStr
= rAttribs
.getString( XML_ref
, OUString() );
677 AddressConverter::convertToCellRangeUnchecked(maRange
, aRangeStr
, nSheet
, getScDocument());
679 maSortCustomList
= rAttribs
.getString( XML_customList
, OUString() );
680 mbDescending
= rAttribs
.getBool( XML_descending
, false );
685 AutoFilter::AutoFilter( const WorkbookHelper
& rHelper
) :
686 WorkbookHelper( rHelper
)
690 void AutoFilter::importAutoFilter( const AttributeList
& rAttribs
, sal_Int16 nSheet
)
692 OUString aRangeStr
= rAttribs
.getString( XML_ref
, OUString() );
693 AddressConverter::convertToCellRangeUnchecked(maRange
, aRangeStr
, nSheet
, getScDocument());
696 void AutoFilter::importAutoFilter( SequenceInputStream
& rStrm
, sal_Int16 nSheet
)
700 AddressConverter::convertToCellRangeUnchecked( maRange
, aBinRange
, nSheet
);
703 void AutoFilter::importSortState( const AttributeList
& rAttribs
, sal_Int16 nSheet
)
705 OUString aRangeStr
= rAttribs
.getString( XML_ref
, OUString() );
706 AddressConverter::convertToCellRangeUnchecked(maSortRange
, aRangeStr
, nSheet
, getScDocument());
709 FilterColumn
& AutoFilter::createFilterColumn()
711 FilterColumnVector::value_type xFilterColumn
= std::make_shared
<FilterColumn
>( *this );
712 maFilterColumns
.push_back( xFilterColumn
);
713 return *xFilterColumn
;
716 SortCondition
& AutoFilter::createSortCondition()
718 SortConditionVector::value_type xSortCondition
= std::make_shared
<SortCondition
>( *this );
719 maSortConditions
.push_back( xSortCondition
);
720 return *xSortCondition
;
723 void AutoFilter::finalizeImport( const Reference
< XDatabaseRange
>& rxDatabaseRange
, sal_Int16 nSheet
)
725 // convert filter settings using the filter descriptor of the database range
726 const Reference
<XSheetFilterDescriptor3
> xFilterDesc( rxDatabaseRange
->getFilterDescriptor(), UNO_QUERY_THROW
);
727 if( !xFilterDesc
.is() )
730 // set some common properties for the auto filter range
731 PropertySet
aDescProps( xFilterDesc
);
732 aDescProps
.setProperty( PROP_IsCaseSensitive
, false );
733 aDescProps
.setProperty( PROP_SkipDuplicates
, false );
734 aDescProps
.setProperty( PROP_Orientation
, TableOrientation_ROWS
);
735 aDescProps
.setProperty( PROP_ContainsHeader
, true );
736 aDescProps
.setProperty( PROP_CopyOutputData
, false );
738 // resulting list of all UNO API filter fields
739 ::std::vector
<TableFilterField3
> aFilterFields
;
741 // track if columns require to enable or disable regular expressions
742 std::optional
< bool > obNeedsRegExp
;
744 /* Track whether the filter fields of the first filter column are
745 connected with 'or'. In this case, other filter fields cannot be
746 inserted without altering the result of the entire filter, due to
747 Calc's precedence for the 'and' connection operator. Example:
748 Excel's filter conditions 'A1 and (B1 or B2) and C1' where B1 and
749 B2 belong to filter column B, will be evaluated by Calc as
750 '(A1 and B1) or (B2 and C1)'. */
751 bool bHasOrConnection
= false;
753 ScDocument
& rDoc
= getScDocument();
754 SCCOL nCol
= maRange
.aStart
.Col();
755 SCROW nRow
= maRange
.aStart
.Row();
756 SCTAB nTab
= maRange
.aStart
.Tab();
758 // process all filter column objects, exit when 'or' connection exists
759 for( const auto& rxFilterColumn
: maFilterColumns
)
761 // the filter settings object creates a list of filter fields
762 ApiFilterSettings aSettings
= rxFilterColumn
->finalizeImport();
763 ApiFilterSettings::FilterFieldVector
& rColumnFields
= aSettings
.maFilterFields
;
765 if (rxFilterColumn
->isButtonHidden())
767 auto nFlag
= rDoc
.GetAttr(nCol
, nRow
, nTab
, ATTR_MERGE_FLAG
)->GetValue();
768 rDoc
.ApplyAttr(nCol
, nRow
, nTab
, ScMergeFlagAttr(nFlag
& ~ScMF::Auto
));
772 /* Check whether mode for regular expressions is compatible with
773 the global mode in obNeedsRegExp. If either one is still in
774 don't-care state, all is fine. If both are set, they must be
776 bool bRegExpCompatible
= !obNeedsRegExp
.has_value() || !aSettings
.mobNeedsRegExp
.has_value() || (obNeedsRegExp
.value() == aSettings
.mobNeedsRegExp
.value());
778 // check whether fields are connected by 'or' (see comments above).
779 if( rColumnFields
.size() >= 2 )
780 bHasOrConnection
= std::any_of(rColumnFields
.begin() + 1, rColumnFields
.end(),
781 [](const css::sheet::TableFilterField3
& rColumnField
) { return rColumnField
.Connection
== FilterConnection_OR
; });
783 /* Skip the column filter, if no filter fields have been created,
784 and if the mode for regular expressions of the
785 filter column does not fit. */
786 if( !rColumnFields
.empty() && bRegExpCompatible
)
788 /* Add 'and' connection to the first filter field to connect
789 it to the existing filter fields of other columns. */
790 rColumnFields
[ 0 ].Connection
= FilterConnection_AND
;
792 // insert the new filter fields
793 aFilterFields
.insert( aFilterFields
.end(), rColumnFields
.begin(), rColumnFields
.end() );
795 // update the regular expressions mode
796 assignIfUsed( obNeedsRegExp
, aSettings
.mobNeedsRegExp
);
799 if( bHasOrConnection
)
803 // insert all filter fields to the filter descriptor
804 if( !aFilterFields
.empty() )
805 xFilterDesc
->setFilterFields3( comphelper::containerToSequence( aFilterFields
) );
807 // regular expressions
808 bool bUseRegExp
= obNeedsRegExp
.value_or( false );
809 aDescProps
.setProperty( PROP_UseRegularExpressions
, bUseRegExp
);
812 if (maSortConditions
.empty())
815 const SortConditionVector::value_type
& xSortConditionPointer
= *maSortConditions
.begin();
816 const SortCondition
& rSorConditionLoaded
= *xSortConditionPointer
;
819 aParam
.bUserDef
= false;
820 aParam
.nUserIndex
= 0;
821 aParam
.bByRow
= false;
823 ScUserList
& rUserList
= ScGlobal::GetUserList();
824 if (!rSorConditionLoaded
.maSortCustomList
.isEmpty())
826 for (size_t i
=0; i
< rUserList
.size(); i
++)
828 const OUString
aEntry(rUserList
[i
].GetString());
829 if (aEntry
.equalsIgnoreAsciiCase(rSorConditionLoaded
.maSortCustomList
))
831 aParam
.bUserDef
= true;
832 aParam
.nUserIndex
= i
;
838 if (!aParam
.bUserDef
)
840 rUserList
.emplace_back(rSorConditionLoaded
.maSortCustomList
);
841 aParam
.bUserDef
= true;
842 aParam
.nUserIndex
= rUserList
.size()-1;
845 // set sort parameter if we have detected it
846 if (!aParam
.bUserDef
)
849 SCCOLROW nStartPos
= aParam
.bByRow
? maRange
.aStart
.Col() : maRange
.aStart
.Row();
850 // descending sort - need to enable 1st SortParam slot
851 if (rSorConditionLoaded
.mbDescending
)
852 assert(aParam
.GetSortKeyCount() == DEFSORT
);
854 aParam
.maKeyState
[0].bDoSort
= true;
855 aParam
.maKeyState
[0].bAscending
= !rSorConditionLoaded
.mbDescending
;
856 aParam
.maKeyState
[0].nField
+= nStartPos
;
858 ScDBData
* pDBData
= rDoc
.GetDBAtArea(
860 maRange
.aStart
.Col(), maRange
.aStart
.Row(),
861 maRange
.aEnd
.Col(), maRange
.aEnd
.Row());
864 pDBData
->SetSortParam(aParam
);
866 OSL_FAIL("AutoFilter::finalizeImport(): cannot find matching DBData");
869 AutoFilterBuffer::AutoFilterBuffer( const WorkbookHelper
& rHelper
) :
870 WorkbookHelper( rHelper
)
874 AutoFilter
& AutoFilterBuffer::createAutoFilter()
876 AutoFilterVector::value_type xAutoFilter
= std::make_shared
<AutoFilter
>( *this );
877 maAutoFilters
.push_back( xAutoFilter
);
881 void AutoFilterBuffer::finalizeImport( sal_Int16 nSheet
)
883 // rely on existence of the defined name '_FilterDatabase' containing the range address of the filtered area
884 const DefinedName
* pFilterDBName
= getDefinedNames().getByBuiltinId( BIFF_DEFNAME_FILTERDATABASE
, nSheet
).get();
888 ScRange aFilterRange
;
889 if( !(pFilterDBName
->getAbsoluteRange( aFilterRange
) && (aFilterRange
.aStart
.Tab() == nSheet
)) )
892 // use the same name for the database range as used for the defined name '_FilterDatabase'
893 Reference
< XDatabaseRange
> xDatabaseRange
= createUnnamedDatabaseRangeObject( aFilterRange
);
894 // first, try to create an auto filter
895 bool bHasAutoFilter
= finalizeImport( xDatabaseRange
, nSheet
);
896 // no success: try to create an advanced filter
897 if( bHasAutoFilter
|| !xDatabaseRange
.is() )
900 // the built-in defined name 'Criteria' must exist
901 const DefinedName
* pCriteriaName
= getDefinedNames().getByBuiltinId( BIFF_DEFNAME_CRITERIA
, nSheet
).get();
905 ScRange aCriteriaRange
;
906 if( !pCriteriaName
->getAbsoluteRange( aCriteriaRange
) )
909 // set some common properties for the filter descriptor
910 PropertySet
aDescProps( xDatabaseRange
->getFilterDescriptor() );
911 aDescProps
.setProperty( PROP_IsCaseSensitive
, false );
912 aDescProps
.setProperty( PROP_SkipDuplicates
, false );
913 aDescProps
.setProperty( PROP_Orientation
, TableOrientation_ROWS
);
914 aDescProps
.setProperty( PROP_ContainsHeader
, true );
915 // criteria range may contain wildcards, but these are incompatible with REs
916 aDescProps
.setProperty( PROP_UseRegularExpressions
, false );
918 // position of output data (if built-in defined name 'Extract' exists)
919 DefinedNameRef xExtractName
= getDefinedNames().getByBuiltinId( BIFF_DEFNAME_EXTRACT
, nSheet
);
920 ScRange aOutputRange
;
921 bool bHasOutputRange
= xExtractName
&& xExtractName
->getAbsoluteRange( aOutputRange
);
922 aDescProps
.setProperty( PROP_CopyOutputData
, bHasOutputRange
);
923 if( bHasOutputRange
)
925 aDescProps
.setProperty( PROP_SaveOutputPosition
, true );
926 aDescProps
.setProperty( PROP_OutputPosition
, CellAddress( aOutputRange
.aStart
.Tab(), aOutputRange
.aStart
.Col(), aOutputRange
.aStart
.Row() ) );
929 /* Properties of the database range (must be set after
930 modifying properties of the filter descriptor,
931 otherwise the 'FilterCriteriaSource' property gets
933 PropertySet
aRangeProps( xDatabaseRange
);
934 aRangeProps
.setProperty( PROP_AutoFilter
, false );
935 aRangeProps
.setProperty( PROP_FilterCriteriaSource
,
936 CellRangeAddress( aCriteriaRange
.aStart
.Tab(),
937 aCriteriaRange
.aStart
.Col(), aCriteriaRange
.aStart
.Row(),
938 aCriteriaRange
.aEnd
.Col(), aCriteriaRange
.aEnd
.Row() ));
941 bool AutoFilterBuffer::finalizeImport( const Reference
< XDatabaseRange
>& rxDatabaseRange
, sal_Int16 nSheet
)
943 AutoFilter
* pAutoFilter
= getActiveAutoFilter();
944 if( pAutoFilter
&& rxDatabaseRange
.is() ) try
946 // the property 'AutoFilter' enables the drop-down buttons
947 PropertySet
aRangeProps( rxDatabaseRange
);
948 aRangeProps
.setProperty( PROP_AutoFilter
, true );
950 pAutoFilter
->finalizeImport( rxDatabaseRange
, nSheet
);
952 // return true to indicate enabled autofilter
961 AutoFilter
* AutoFilterBuffer::getActiveAutoFilter()
963 // Excel expects not more than one auto filter per sheet or table
964 OSL_ENSURE( maAutoFilters
.size() <= 1, "AutoFilterBuffer::getActiveAutoFilter - too many auto filters" );
965 // stick to the last imported auto filter
966 return maAutoFilters
.empty() ? nullptr : maAutoFilters
.back().get();
971 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */