summaryrefslogtreecommitdiffstats
path: root/sc/source/filter/oox/autofilterbuffer.cxx
diff options
context:
space:
mode:
Diffstat (limited to 'sc/source/filter/oox/autofilterbuffer.cxx')
-rw-r--r--sc/source/filter/oox/autofilterbuffer.cxx955
1 files changed, 955 insertions, 0 deletions
diff --git a/sc/source/filter/oox/autofilterbuffer.cxx b/sc/source/filter/oox/autofilterbuffer.cxx
new file mode 100644
index 000000000..4fc354960
--- /dev/null
+++ b/sc/source/filter/oox/autofilterbuffer.cxx
@@ -0,0 +1,955 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * 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/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <autofilterbuffer.hxx>
+
+#include <com/sun/star/beans/XPropertySet.hpp>
+#include <com/sun/star/sheet/FilterFieldType.hpp>
+#include <com/sun/star/sheet/FilterConnection.hpp>
+#include <com/sun/star/sheet/FilterOperator2.hpp>
+#include <com/sun/star/sheet/TableFilterField3.hpp>
+#include <com/sun/star/sheet/XDatabaseRange.hpp>
+#include <com/sun/star/sheet/XSheetFilterDescriptor3.hpp>
+#include <com/sun/star/table/TableOrientation.hpp>
+#include <com/sun/star/table/CellAddress.hpp>
+
+#include <comphelper/sequence.hxx>
+#include <editeng/brushitem.hxx>
+#include <rtl/ustrbuf.hxx>
+#include <osl/diagnose.h>
+#include <oox/helper/attributelist.hxx>
+#include <oox/helper/propertyset.hxx>
+#include <oox/helper/binaryinputstream.hxx>
+#include <oox/token/namespaces.hxx>
+#include <oox/token/properties.hxx>
+#include <oox/token/tokens.hxx>
+#include <addressconverter.hxx>
+#include <defnamesbuffer.hxx>
+#include <biffhelper.hxx>
+#include <document.hxx>
+#include <dbdata.hxx>
+#include <scitems.hxx>
+#include <sortparam.hxx>
+#include <stlpool.hxx>
+#include <stlsheet.hxx>
+#include <stylesbuffer.hxx>
+#include <userlist.hxx>
+
+namespace oox::xls {
+
+using namespace css;
+using namespace ::com::sun::star::sheet;
+using namespace ::com::sun::star::table;
+using namespace ::com::sun::star::uno;
+
+namespace {
+
+const sal_uInt8 BIFF12_TOP10FILTER_TOP = 0x01;
+const sal_uInt8 BIFF12_TOP10FILTER_PERCENT = 0x02;
+
+const sal_uInt16 BIFF12_FILTERCOLUMN_HIDDENBUTTON = 0x0001;
+const sal_uInt16 BIFF12_FILTERCOLUMN_SHOWBUTTON = 0x0002;
+
+const sal_uInt8 BIFF_FILTER_DATATYPE_NONE = 0;
+const sal_uInt8 BIFF_FILTER_DATATYPE_DOUBLE = 4;
+const sal_uInt8 BIFF_FILTER_DATATYPE_STRING = 6;
+const sal_uInt8 BIFF_FILTER_DATATYPE_BOOLEAN = 8;
+const sal_uInt8 BIFF_FILTER_DATATYPE_EMPTY = 12;
+const sal_uInt8 BIFF_FILTER_DATATYPE_NOTEMPTY = 14;
+
+bool lclGetApiOperatorFromToken( sal_Int32& rnApiOperator, sal_Int32 nToken )
+{
+ switch( nToken )
+ {
+ case XML_lessThan: rnApiOperator = FilterOperator2::LESS; return true;
+ case XML_equal: rnApiOperator = FilterOperator2::EQUAL; return true;
+ case XML_lessThanOrEqual: rnApiOperator = FilterOperator2::LESS_EQUAL; return true;
+ case XML_greaterThan: rnApiOperator = FilterOperator2::GREATER; return true;
+ case XML_notEqual: rnApiOperator = FilterOperator2::NOT_EQUAL; return true;
+ case XML_greaterThanOrEqual: rnApiOperator = FilterOperator2::GREATER_EQUAL; return true;
+ }
+ return false;
+}
+
+/** Removes leading asterisk characters from the passed string.
+ @return True = at least one asterisk character has been removed. */
+bool lclTrimLeadingAsterisks( OUString& rValue )
+{
+ sal_Int32 nLength = rValue.getLength();
+ sal_Int32 nPos = 0;
+ while( (nPos < nLength) && (rValue[ nPos ] == '*') )
+ ++nPos;
+ if( nPos > 0 )
+ {
+ rValue = rValue.copy( nPos );
+ return true;
+ }
+ return false;
+}
+
+/** Removes trailing asterisk characters from the passed string.
+ @return True = at least one asterisk character has been removed. */
+bool lclTrimTrailingAsterisks( OUString& rValue )
+{
+ sal_Int32 nLength = rValue.getLength();
+ sal_Int32 nPos = nLength;
+ while( (nPos > 0) && (rValue[ nPos - 1 ] == '*') )
+ --nPos;
+ if( nPos < nLength )
+ {
+ rValue = rValue.copy( 0, nPos );
+ return true;
+ }
+ return false;
+}
+
+/** Converts wildcard characters '*' and '?' to regular expressions and quotes
+ RE meta characters.
+ @return True = passed string has been changed (RE needs to be enabled). */
+bool lclConvertWildcardsToRegExp( OUString& rValue )
+{
+ // check existence of the wildcard characters '*' and '?'
+ if( !rValue.isEmpty() && ((rValue.indexOf( '*' ) >= 0) || (rValue.indexOf( '?' ) >= 0)) )
+ {
+ OUStringBuffer aBuffer;
+ aBuffer.ensureCapacity( rValue.getLength() + 5 );
+ const sal_Unicode* pcChar = rValue.getStr();
+ const sal_Unicode* pcEnd = pcChar + rValue.getLength();
+ for( ; pcChar < pcEnd; ++pcChar )
+ {
+ switch( *pcChar )
+ {
+ case '?':
+ aBuffer.append( '.' );
+ break;
+ case '*':
+ aBuffer.append( '.' ).append( '*' );
+ break;
+ case '\\': case '.': case '|': case '(': case ')': case '^': case '$':
+ // quote RE meta characters
+ aBuffer.append( '\\' ).append( *pcChar );
+ break;
+ default:
+ aBuffer.append( *pcChar );
+ }
+ }
+ rValue = aBuffer.makeStringAndClear();
+ return true;
+ }
+ return false;
+}
+
+} // namespace
+
+ApiFilterSettings::ApiFilterSettings()
+{
+}
+
+void ApiFilterSettings::appendField( bool bAnd, sal_Int32 nOperator, double fValue )
+{
+ maFilterFields.emplace_back();
+ TableFilterField3& rFilterField = maFilterFields.back();
+ rFilterField.Connection = bAnd ? FilterConnection_AND : FilterConnection_OR;
+ rFilterField.Operator = nOperator;
+ rFilterField.Values.realloc(1);
+ auto pValues = rFilterField.Values.getArray();
+ pValues[0].FilterType = FilterFieldType::NUMERIC;
+ pValues[0].NumericValue = fValue;
+}
+
+void ApiFilterSettings::appendField( bool bAnd, sal_Int32 nOperator, const OUString& rValue )
+{
+ maFilterFields.emplace_back();
+ TableFilterField3& rFilterField = maFilterFields.back();
+ rFilterField.Connection = bAnd ? FilterConnection_AND : FilterConnection_OR;
+ rFilterField.Operator = nOperator;
+ rFilterField.Values.realloc(1);
+ auto pValues = rFilterField.Values.getArray();
+ pValues[0].FilterType = FilterFieldType::STRING;
+ pValues[0].StringValue = rValue;
+}
+
+void ApiFilterSettings::appendField(bool bAnd, util::Color aColor, bool bIsBackgroundColor)
+{
+ maFilterFields.emplace_back();
+ TableFilterField3& rFilterField = maFilterFields.back();
+ rFilterField.Connection = bAnd ? FilterConnection_AND : FilterConnection_OR;
+ rFilterField.Operator = FilterOperator2::EQUAL;
+ rFilterField.Values.realloc(1);
+ auto pValues = rFilterField.Values.getArray();
+ pValues[0].FilterType
+ = bIsBackgroundColor ? FilterFieldType::BACKGROUND_COLOR : FilterFieldType::TEXT_COLOR;
+ pValues[0].ColorValue = aColor;
+}
+
+void ApiFilterSettings::appendField( bool bAnd, const std::vector<std::pair<OUString, bool>>& rValues )
+{
+ maFilterFields.emplace_back();
+ TableFilterField3& rFilterField = maFilterFields.back();
+ rFilterField.Connection = bAnd ? FilterConnection_AND : FilterConnection_OR;
+ rFilterField.Operator = FilterOperator2::EQUAL;
+ rFilterField.Values.realloc(rValues.size());
+ auto pValues = rFilterField.Values.getArray();
+ size_t i = 0;
+
+ for( auto const& it : rValues )
+ {
+ pValues[i].StringValue = it.first;
+ pValues[i++].FilterType
+ = it.second ? FilterFieldType::DATE : FilterFieldType::STRING;
+ }
+}
+
+FilterSettingsBase::FilterSettingsBase( const WorkbookHelper& rHelper ) :
+ WorkbookHelper( rHelper )
+{
+}
+
+void FilterSettingsBase::importAttribs( sal_Int32 /*nElement*/, const AttributeList& /*rAttribs*/ )
+{
+}
+
+void FilterSettingsBase::importRecord( sal_Int32 /*nRecId*/, SequenceInputStream& /*rStrm*/ )
+{
+}
+
+ApiFilterSettings FilterSettingsBase::finalizeImport()
+{
+ return ApiFilterSettings();
+}
+
+DiscreteFilter::DiscreteFilter( const WorkbookHelper& rHelper ) :
+ FilterSettingsBase( rHelper ),
+ mnCalendarType( XML_none ),
+ mbShowBlank( false )
+{
+}
+
+void DiscreteFilter::importAttribs( sal_Int32 nElement, const AttributeList& rAttribs )
+{
+ switch( nElement )
+ {
+ case XLS_TOKEN( filters ):
+ mnCalendarType = rAttribs.getToken( XML_calendarType, XML_none );
+ mbShowBlank = rAttribs.getBool( XML_blank, false );
+ break;
+
+ case XLS_TOKEN( filter ):
+ {
+ OUString aValue = rAttribs.getXString( XML_val, OUString() );
+ if( !aValue.isEmpty() )
+ maValues.push_back( std::make_pair(aValue, false) );
+ }
+ break;
+
+ case XLS_TOKEN( dateGroupItem ):
+ {
+ OUString aDateValue;
+ // it is just a fallback, we do not need the XML_day as default value,
+ // because if the dateGroupItem exists also XML_dateTimeGrouping exists!
+ sal_uInt16 nToken = rAttribs.getToken(XML_dateTimeGrouping, XML_day);
+ if( nToken == XML_year || nToken == XML_month || nToken == XML_day ||
+ nToken == XML_hour || nToken == XML_minute || nToken == XML_second )
+ {
+ aDateValue = rAttribs.getString(XML_year, OUString());
+
+ if( nToken == XML_month || nToken == XML_day || nToken == XML_hour ||
+ nToken == XML_minute || nToken == XML_second )
+ {
+ OUString aMonthName = rAttribs.getString(XML_month, OUString());
+ if( aMonthName.getLength() == 1 )
+ aMonthName = "0" + aMonthName;
+ aDateValue += "-" + aMonthName;
+
+ if( nToken == XML_day || nToken == XML_hour || nToken == XML_minute ||
+ nToken == XML_second )
+ {
+ OUString aDayName = rAttribs.getString(XML_day, OUString());
+ if( aDayName.getLength() == 1 )
+ aDayName = "0" + aDayName;
+ aDateValue += "-" + aDayName;
+
+ if( nToken == XML_hour || nToken == XML_minute || nToken == XML_second )
+ {
+ OUString aHourName = rAttribs.getString(XML_hour, OUString());
+ if( aHourName.getLength() == 1 )
+ aHourName = "0" + aHourName;
+ aDateValue += " " + aHourName;
+
+ if( nToken == XML_minute || nToken == XML_second )
+ {
+ OUString aMinName = rAttribs.getString(XML_minute, OUString());
+ if( aMinName.getLength() == 1 )
+ aMinName = "0" + aMinName;
+ aDateValue += ":" + aMinName;
+
+ if( nToken == XML_second )
+ {
+ OUString aSecName = rAttribs.getString(XML_second, OUString());
+ if( aSecName.getLength() == 1 )
+ aSecName = "0" + aSecName;
+ aDateValue += ":" + aSecName;
+ }
+ }
+ }
+ }
+ }
+ }
+ if( !aDateValue.isEmpty() )
+ maValues.push_back( std::make_pair(aDateValue, true) );
+ }
+ break;
+ }
+}
+
+void DiscreteFilter::importRecord( sal_Int32 nRecId, SequenceInputStream& rStrm )
+{
+ switch( nRecId )
+ {
+ case BIFF12_ID_DISCRETEFILTERS:
+ {
+ sal_Int32 nShowBlank, nCalendarType;
+ nShowBlank = rStrm.readInt32();
+ nCalendarType = rStrm.readInt32();
+
+ static const sal_Int32 spnCalendarTypes[] = {
+ XML_none, XML_gregorian, XML_gregorianUs, XML_japan, XML_taiwan, XML_korea, XML_hijri, XML_thai, XML_hebrew,
+ XML_gregorianMeFrench, XML_gregorianArabic, XML_gregorianXlitEnglish, XML_gregorianXlitFrench };
+ mnCalendarType = STATIC_ARRAY_SELECT( spnCalendarTypes, nCalendarType, XML_none );
+ mbShowBlank = nShowBlank != 0;
+ }
+ break;
+
+ case BIFF12_ID_DISCRETEFILTER:
+ {
+ OUString aValue = BiffHelper::readString( rStrm );
+ if( !aValue.isEmpty() )
+ maValues.push_back( std::make_pair(aValue, false) );
+ }
+ break;
+ }
+}
+
+ApiFilterSettings DiscreteFilter::finalizeImport()
+{
+ ApiFilterSettings aSettings;
+ aSettings.maFilterFields.reserve( maValues.size() );
+
+ // insert all filter values
+ aSettings.appendField( true, maValues );
+
+ // extra field for 'show empty'
+ if( mbShowBlank )
+ aSettings.appendField( false, FilterOperator2::EMPTY, OUString() );
+
+ /* Require disabled regular expressions, filter entries may contain
+ any RE meta characters. */
+ if( !maValues.empty() )
+ aSettings.mobNeedsRegExp = false;
+
+ return aSettings;
+}
+
+Top10Filter::Top10Filter( const WorkbookHelper& rHelper ) :
+ FilterSettingsBase( rHelper ),
+ mfValue( 0.0 ),
+ mbTop( true ),
+ mbPercent( false )
+{
+}
+
+void Top10Filter::importAttribs( sal_Int32 nElement, const AttributeList& rAttribs )
+{
+ if( nElement == XLS_TOKEN( top10 ) )
+ {
+ mfValue = rAttribs.getDouble( XML_val, 0.0 );
+ mbTop = rAttribs.getBool( XML_top, true );
+ mbPercent = rAttribs.getBool( XML_percent, false );
+ }
+}
+
+void Top10Filter::importRecord( sal_Int32 nRecId, SequenceInputStream& rStrm )
+{
+ if( nRecId == BIFF12_ID_TOP10FILTER )
+ {
+ sal_uInt8 nFlags;
+ nFlags = rStrm.readuChar();
+ mfValue = rStrm.readDouble();
+ mbTop = getFlag( nFlags, BIFF12_TOP10FILTER_TOP );
+ mbPercent = getFlag( nFlags, BIFF12_TOP10FILTER_PERCENT );
+ }
+}
+
+ApiFilterSettings Top10Filter::finalizeImport()
+{
+ sal_Int32 nOperator = mbTop ?
+ (mbPercent ? FilterOperator2::TOP_PERCENT : FilterOperator2::TOP_VALUES) :
+ (mbPercent ? FilterOperator2::BOTTOM_PERCENT : FilterOperator2::BOTTOM_VALUES);
+ ApiFilterSettings aSettings;
+ aSettings.appendField( true, nOperator, mfValue );
+ return aSettings;
+}
+
+ColorFilter::ColorFilter(const WorkbookHelper& rHelper)
+ : FilterSettingsBase(rHelper)
+ , mbIsBackgroundColor(false)
+{
+}
+
+void ColorFilter::importAttribs(sal_Int32 nElement, const AttributeList& rAttribs)
+{
+ if (nElement == XLS_TOKEN(colorFilter))
+ {
+ // When cellColor attribute not found, it means cellColor = true
+ // cellColor = 0 (false) -> TextColor
+ // cellColor = 1 (true) -> BackgroundColor
+ mbIsBackgroundColor = rAttribs.getBool(XML_cellColor, true);
+ msStyleName = getStyles().createDxfStyle( rAttribs.getInteger(XML_dxfId, -1) );
+ }
+}
+
+void ColorFilter::importRecord(sal_Int32 /* nRecId */, SequenceInputStream& /* rStrm */)
+{
+ // TODO
+}
+
+ApiFilterSettings ColorFilter::finalizeImport()
+{
+ ApiFilterSettings aSettings;
+ ScDocument& rDoc = getScDocument();
+ ScStyleSheet* pStyleSheet = static_cast<ScStyleSheet*>(
+ rDoc.GetStyleSheetPool()->Find(msStyleName, SfxStyleFamily::Para));
+ if (!pStyleSheet)
+ return aSettings;
+
+ const SfxItemSet& rItemSet = pStyleSheet->GetItemSet();
+ // Color (whether text or background color) is always stored in ATTR_BACKGROUND
+ const SvxBrushItem* pItem = rItemSet.GetItem<SvxBrushItem>(ATTR_BACKGROUND);
+ ::Color aColor = pItem->GetFiltColor();
+ util::Color nColor(aColor);
+ aSettings.appendField(true, nColor, mbIsBackgroundColor);
+ return aSettings;
+}
+
+FilterCriterionModel::FilterCriterionModel() :
+ mnOperator( XML_equal ),
+ mnDataType( BIFF_FILTER_DATATYPE_NONE )
+{
+}
+
+void FilterCriterionModel::setBiffOperator( sal_uInt8 nOperator )
+{
+ static const sal_Int32 spnOperators[] = { XML_TOKEN_INVALID,
+ XML_lessThan, XML_equal, XML_lessThanOrEqual, XML_greaterThan, XML_notEqual, XML_greaterThanOrEqual };
+ mnOperator = STATIC_ARRAY_SELECT( spnOperators, nOperator, XML_TOKEN_INVALID );
+}
+
+void FilterCriterionModel::readBiffData( SequenceInputStream& rStrm )
+{
+ sal_uInt8 nOperator;
+ mnDataType = rStrm.readuChar();
+ nOperator = rStrm.readuChar();
+ setBiffOperator( nOperator );
+
+ switch( mnDataType )
+ {
+ case BIFF_FILTER_DATATYPE_DOUBLE:
+ maValue <<= rStrm.readDouble();
+ break;
+ case BIFF_FILTER_DATATYPE_STRING:
+ {
+ rStrm.skip( 8 );
+ OUString aValue = BiffHelper::readString( rStrm ).trim();
+ if( !aValue.isEmpty() )
+ maValue <<= aValue;
+ }
+ break;
+ case BIFF_FILTER_DATATYPE_BOOLEAN:
+ maValue <<= (rStrm.readuInt8() != 0);
+ rStrm.skip( 7 );
+ break;
+ case BIFF_FILTER_DATATYPE_EMPTY:
+ rStrm.skip( 8 );
+ if( mnOperator == XML_equal )
+ maValue <<= OUString();
+ break;
+ case BIFF_FILTER_DATATYPE_NOTEMPTY:
+ rStrm.skip( 8 );
+ if( mnOperator == XML_notEqual )
+ maValue <<= OUString();
+ break;
+ default:
+ OSL_ENSURE( false, "FilterCriterionModel::readBiffData - unexpected data type" );
+ rStrm.skip( 8 );
+ }
+}
+
+CustomFilter::CustomFilter( const WorkbookHelper& rHelper ) :
+ FilterSettingsBase( rHelper ),
+ mbAnd( false )
+{
+}
+
+void CustomFilter::importAttribs( sal_Int32 nElement, const AttributeList& rAttribs )
+{
+ switch( nElement )
+ {
+ case XLS_TOKEN( customFilters ):
+ mbAnd = rAttribs.getBool( XML_and, false );
+ break;
+
+ case XLS_TOKEN( customFilter ):
+ {
+ FilterCriterionModel aCriterion;
+ aCriterion.mnOperator = rAttribs.getToken( XML_operator, XML_equal );
+ OUString aValue = rAttribs.getXString( XML_val, OUString() ).trim();
+ if( (aCriterion.mnOperator == XML_equal) || (aCriterion.mnOperator == XML_notEqual) || (!aValue.isEmpty()) )
+ aCriterion.maValue <<= aValue;
+ appendCriterion( aCriterion );
+ }
+ break;
+ }
+}
+
+void CustomFilter::importRecord( sal_Int32 nRecId, SequenceInputStream& rStrm )
+{
+ switch( nRecId )
+ {
+ case BIFF12_ID_CUSTOMFILTERS:
+ mbAnd = rStrm.readInt32() == 0;
+ break;
+
+ case BIFF12_ID_CUSTOMFILTER:
+ {
+ FilterCriterionModel aCriterion;
+ aCriterion.readBiffData( rStrm );
+ appendCriterion( aCriterion );
+ }
+ break;
+ }
+}
+
+ApiFilterSettings CustomFilter::finalizeImport()
+{
+ ApiFilterSettings aSettings;
+ OSL_ENSURE( maCriteria.size() <= 2, "CustomFilter::finalizeImport - too many filter criteria" );
+ for( const auto& rCriterion : maCriteria )
+ {
+ // first extract the filter operator
+ sal_Int32 nOperator = 0;
+ bool bValidOperator = lclGetApiOperatorFromToken( nOperator, rCriterion.mnOperator );
+ if( bValidOperator )
+ {
+ if( rCriterion.maValue.has< OUString >() )
+ {
+ // string argument
+ OUString aValue;
+ rCriterion.maValue >>= aValue;
+ // check for 'empty', 'contains', 'begins with', or 'ends with' text filters
+ bool bEqual = nOperator == FilterOperator2::EQUAL;
+ bool bNotEqual = nOperator == FilterOperator2::NOT_EQUAL;
+ if( bEqual || bNotEqual )
+ {
+ if( aValue.isEmpty() )
+ {
+ // empty comparison string: create empty/not empty filters
+ nOperator = bNotEqual ? FilterOperator2::NOT_EMPTY : FilterOperator2::EMPTY;
+ }
+ else
+ {
+ // compare to something: try to find begins/ends/contains
+ bool bHasLeadingAsterisk = lclTrimLeadingAsterisks( aValue );
+ bool bHasTrailingAsterisk = lclTrimTrailingAsterisks( aValue );
+ // just '***' matches everything, do not create a filter field
+ bValidOperator = !aValue.isEmpty();
+ if( bValidOperator )
+ {
+ if( bHasLeadingAsterisk && bHasTrailingAsterisk )
+ nOperator = bNotEqual ? FilterOperator2::DOES_NOT_CONTAIN : FilterOperator2::CONTAINS;
+ else if( bHasLeadingAsterisk )
+ nOperator = bNotEqual ? FilterOperator2::DOES_NOT_END_WITH : FilterOperator2::ENDS_WITH;
+ else if( bHasTrailingAsterisk )
+ nOperator = bNotEqual ? FilterOperator2::DOES_NOT_BEGIN_WITH : FilterOperator2::BEGINS_WITH;
+ // else: no asterisks, stick to equal/not equal
+ }
+ }
+ }
+
+ if( bValidOperator )
+ {
+ // if wildcards are present, require RE mode, otherwise keep don't care state
+ if( lclConvertWildcardsToRegExp( aValue ) )
+ aSettings.mobNeedsRegExp = true;
+ // create a new UNO API filter field
+ aSettings.appendField( mbAnd, nOperator, aValue );
+ }
+ }
+ else if( rCriterion.maValue.has< double >() )
+ {
+ // floating-point argument
+ double fValue = 0.0;
+ rCriterion.maValue >>= fValue;
+ aSettings.appendField( mbAnd, nOperator, fValue );
+ }
+ }
+ }
+ return aSettings;
+}
+
+void CustomFilter::appendCriterion( const FilterCriterionModel& rCriterion )
+{
+ if( (rCriterion.mnOperator != XML_TOKEN_INVALID) && rCriterion.maValue.hasValue() )
+ maCriteria.push_back( rCriterion );
+}
+
+FilterColumn::FilterColumn( const WorkbookHelper& rHelper ) :
+ WorkbookHelper( rHelper ),
+ mnColId( -1 ),
+ mbHiddenButton( false ),
+ mbShowButton( true )
+{
+}
+
+void FilterColumn::importFilterColumn( const AttributeList& rAttribs )
+{
+ mnColId = rAttribs.getInteger( XML_colId, -1 );
+ mbHiddenButton = rAttribs.getBool( XML_hiddenButton, false );
+ mbShowButton = rAttribs.getBool( XML_showButton, true );
+}
+
+void FilterColumn::importFilterColumn( SequenceInputStream& rStrm )
+{
+ sal_uInt16 nFlags;
+ mnColId = rStrm.readInt32();
+ nFlags = rStrm.readuInt16();
+ mbHiddenButton = getFlag( nFlags, BIFF12_FILTERCOLUMN_HIDDENBUTTON );
+ mbShowButton = getFlag( nFlags, BIFF12_FILTERCOLUMN_SHOWBUTTON );
+}
+
+ApiFilterSettings FilterColumn::finalizeImport()
+{
+ ApiFilterSettings aSettings;
+ if( (0 <= mnColId) && mxSettings )
+ {
+ // filter settings object creates a sequence of filter fields
+ aSettings = mxSettings->finalizeImport();
+ // add column index to all filter fields
+ for( auto& rFilterField : aSettings.maFilterFields )
+ rFilterField.Field = mnColId;
+ }
+ return aSettings;
+}
+
+// SortCondition
+
+SortCondition::SortCondition( const WorkbookHelper& rHelper ) :
+ WorkbookHelper( rHelper ),
+ mbDescending( false )
+{
+}
+
+void SortCondition::importSortCondition( const AttributeList& rAttribs, sal_Int16 nSheet )
+{
+ OUString aRangeStr = rAttribs.getString( XML_ref, OUString() );
+ AddressConverter::convertToCellRangeUnchecked( maRange, aRangeStr, nSheet );
+
+ maSortCustomList = rAttribs.getString( XML_customList, OUString() );
+ mbDescending = rAttribs.getBool( XML_descending, false );
+}
+
+// AutoFilter
+
+AutoFilter::AutoFilter( const WorkbookHelper& rHelper ) :
+ WorkbookHelper( rHelper )
+{
+}
+
+void AutoFilter::importAutoFilter( const AttributeList& rAttribs, sal_Int16 nSheet )
+{
+ OUString aRangeStr = rAttribs.getString( XML_ref, OUString() );
+ AddressConverter::convertToCellRangeUnchecked( maRange, aRangeStr, nSheet );
+}
+
+void AutoFilter::importAutoFilter( SequenceInputStream& rStrm, sal_Int16 nSheet )
+{
+ BinRange aBinRange;
+ rStrm >> aBinRange;
+ AddressConverter::convertToCellRangeUnchecked( maRange, aBinRange, nSheet );
+}
+
+void AutoFilter::importSortState( const AttributeList& rAttribs, sal_Int16 nSheet )
+{
+ OUString aRangeStr = rAttribs.getString( XML_ref, OUString() );
+ AddressConverter::convertToCellRangeUnchecked( maSortRange, aRangeStr, nSheet );
+}
+
+FilterColumn& AutoFilter::createFilterColumn()
+{
+ FilterColumnVector::value_type xFilterColumn = std::make_shared<FilterColumn>( *this );
+ maFilterColumns.push_back( xFilterColumn );
+ return *xFilterColumn;
+}
+
+SortCondition& AutoFilter::createSortCondition()
+{
+ SortConditionVector::value_type xSortCondition = std::make_shared<SortCondition>( *this );
+ maSortConditions.push_back( xSortCondition );
+ return *xSortCondition;
+}
+
+void AutoFilter::finalizeImport( const Reference< XDatabaseRange >& rxDatabaseRange, sal_Int16 nSheet )
+{
+ // convert filter settings using the filter descriptor of the database range
+ const Reference<XSheetFilterDescriptor3> xFilterDesc( rxDatabaseRange->getFilterDescriptor(), UNO_QUERY_THROW );
+ if( !xFilterDesc.is() )
+ return;
+
+ // set some common properties for the auto filter range
+ PropertySet aDescProps( xFilterDesc );
+ aDescProps.setProperty( PROP_IsCaseSensitive, false );
+ aDescProps.setProperty( PROP_SkipDuplicates, false );
+ aDescProps.setProperty( PROP_Orientation, TableOrientation_ROWS );
+ aDescProps.setProperty( PROP_ContainsHeader, true );
+ aDescProps.setProperty( PROP_CopyOutputData, false );
+
+ // resulting list of all UNO API filter fields
+ ::std::vector<TableFilterField3> aFilterFields;
+
+ // track if columns require to enable or disable regular expressions
+ OptValue< bool > obNeedsRegExp;
+
+ /* Track whether the filter fields of the first filter column are
+ connected with 'or'. In this case, other filter fields cannot be
+ inserted without altering the result of the entire filter, due to
+ Calc's precedence for the 'and' connection operator. Example:
+ Excel's filter conditions 'A1 and (B1 or B2) and C1' where B1 and
+ B2 belong to filter column B, will be evaluated by Calc as
+ '(A1 and B1) or (B2 and C1)'. */
+ bool bHasOrConnection = false;
+
+ // process all filter column objects, exit when 'or' connection exists
+ for( const auto& rxFilterColumn : maFilterColumns )
+ {
+ // the filter settings object creates a list of filter fields
+ ApiFilterSettings aSettings = rxFilterColumn->finalizeImport();
+ ApiFilterSettings::FilterFieldVector& rColumnFields = aSettings.maFilterFields;
+
+ /* Check whether mode for regular expressions is compatible with
+ the global mode in obNeedsRegExp. If either one is still in
+ don't-care state, all is fine. If both are set, they must be
+ equal. */
+ bool bRegExpCompatible = !obNeedsRegExp || !aSettings.mobNeedsRegExp || (obNeedsRegExp.get() == aSettings.mobNeedsRegExp.get());
+
+ // check whether fields are connected by 'or' (see comments above).
+ if( rColumnFields.size() >= 2 )
+ bHasOrConnection = std::any_of(rColumnFields.begin() + 1, rColumnFields.end(),
+ [](const css::sheet::TableFilterField3& rColumnField) { return rColumnField.Connection == FilterConnection_OR; });
+
+ /* Skip the column filter, if no filter fields have been created,
+ and if the mode for regular expressions of the
+ filter column does not fit. */
+ if( !rColumnFields.empty() && bRegExpCompatible )
+ {
+ /* Add 'and' connection to the first filter field to connect
+ it to the existing filter fields of other columns. */
+ rColumnFields[ 0 ].Connection = FilterConnection_AND;
+
+ // insert the new filter fields
+ aFilterFields.insert( aFilterFields.end(), rColumnFields.begin(), rColumnFields.end() );
+
+ // update the regular expressions mode
+ obNeedsRegExp.assignIfUsed( aSettings.mobNeedsRegExp );
+ }
+
+ if( bHasOrConnection )
+ break;
+ }
+
+ // insert all filter fields to the filter descriptor
+ if( !aFilterFields.empty() )
+ xFilterDesc->setFilterFields3( comphelper::containerToSequence( aFilterFields ) );
+
+ // regular expressions
+ bool bUseRegExp = obNeedsRegExp.get( false );
+ aDescProps.setProperty( PROP_UseRegularExpressions, bUseRegExp );
+
+ // sort
+ if (maSortConditions.empty())
+ return;
+
+ const SortConditionVector::value_type& xSortConditionPointer = *maSortConditions.begin();
+ const SortCondition& rSorConditionLoaded = *xSortConditionPointer;
+
+ ScSortParam aParam;
+ aParam.bUserDef = false;
+ aParam.nUserIndex = 0;
+ aParam.bByRow = false;
+
+ ScUserList* pUserList = ScGlobal::GetUserList();
+ if (!rSorConditionLoaded.maSortCustomList.isEmpty())
+ {
+ for (size_t i=0; pUserList && i < pUserList->size(); i++)
+ {
+ const OUString aEntry((*pUserList)[i].GetString());
+ if (aEntry.equalsIgnoreAsciiCase(rSorConditionLoaded.maSortCustomList))
+ {
+ aParam.bUserDef = true;
+ aParam.nUserIndex = i;
+ break;
+ }
+ }
+ }
+
+ if (!aParam.bUserDef)
+ {
+ pUserList->push_back(new ScUserListData(rSorConditionLoaded.maSortCustomList));
+ aParam.bUserDef = true;
+ aParam.nUserIndex = pUserList->size()-1;
+ }
+
+ // set sort parameter if we have detected it
+ if (!aParam.bUserDef)
+ return;
+
+ SCCOLROW nStartPos = aParam.bByRow ? maRange.aStart.Col() : maRange.aStart.Row();
+ if (rSorConditionLoaded.mbDescending)
+ {
+ // descending sort - need to enable 1st SortParam slot
+ assert(aParam.GetSortKeyCount() == DEFSORT);
+
+ aParam.maKeyState[0].bDoSort = true;
+ aParam.maKeyState[0].bAscending = false;
+ aParam.maKeyState[0].nField += nStartPos;
+ }
+
+ ScDocument& rDoc = getScDocument();
+ ScDBData* pDBData = rDoc.GetDBAtArea(
+ nSheet,
+ maRange.aStart.Col(), maRange.aStart.Row(),
+ maRange.aEnd.Col(), maRange.aEnd.Row());
+
+ if (pDBData)
+ pDBData->SetSortParam(aParam);
+ else
+ OSL_FAIL("AutoFilter::finalizeImport(): cannot find matching DBData");
+}
+
+AutoFilterBuffer::AutoFilterBuffer( const WorkbookHelper& rHelper ) :
+ WorkbookHelper( rHelper )
+{
+}
+
+AutoFilter& AutoFilterBuffer::createAutoFilter()
+{
+ AutoFilterVector::value_type xAutoFilter = std::make_shared<AutoFilter>( *this );
+ maAutoFilters.push_back( xAutoFilter );
+ return *xAutoFilter;
+}
+
+void AutoFilterBuffer::finalizeImport( sal_Int16 nSheet )
+{
+ // rely on existence of the defined name '_FilterDatabase' containing the range address of the filtered area
+ const DefinedName* pFilterDBName = getDefinedNames().getByBuiltinId( BIFF_DEFNAME_FILTERDATABASE, nSheet ).get();
+ if(!pFilterDBName)
+ return;
+
+ ScRange aFilterRange;
+ if( !(pFilterDBName->getAbsoluteRange( aFilterRange ) && (aFilterRange.aStart.Tab() == nSheet)) )
+ return;
+
+ // use the same name for the database range as used for the defined name '_FilterDatabase'
+ Reference< XDatabaseRange > xDatabaseRange = createUnnamedDatabaseRangeObject( aFilterRange );
+ // first, try to create an auto filter
+ bool bHasAutoFilter = finalizeImport( xDatabaseRange, nSheet );
+ // no success: try to create an advanced filter
+ if( bHasAutoFilter || !xDatabaseRange.is() )
+ return;
+
+ // the built-in defined name 'Criteria' must exist
+ const DefinedName* pCriteriaName = getDefinedNames().getByBuiltinId( BIFF_DEFNAME_CRITERIA, nSheet ).get();
+ if( !pCriteriaName )
+ return;
+
+ ScRange aCriteriaRange;
+ if( !pCriteriaName->getAbsoluteRange( aCriteriaRange ) )
+ return;
+
+ // set some common properties for the filter descriptor
+ PropertySet aDescProps( xDatabaseRange->getFilterDescriptor() );
+ aDescProps.setProperty( PROP_IsCaseSensitive, false );
+ aDescProps.setProperty( PROP_SkipDuplicates, false );
+ aDescProps.setProperty( PROP_Orientation, TableOrientation_ROWS );
+ aDescProps.setProperty( PROP_ContainsHeader, true );
+ // criteria range may contain wildcards, but these are incompatible with REs
+ aDescProps.setProperty( PROP_UseRegularExpressions, false );
+
+ // position of output data (if built-in defined name 'Extract' exists)
+ DefinedNameRef xExtractName = getDefinedNames().getByBuiltinId( BIFF_DEFNAME_EXTRACT, nSheet );
+ ScRange aOutputRange;
+ bool bHasOutputRange = xExtractName && xExtractName->getAbsoluteRange( aOutputRange );
+ aDescProps.setProperty( PROP_CopyOutputData, bHasOutputRange );
+ if( bHasOutputRange )
+ {
+ aDescProps.setProperty( PROP_SaveOutputPosition, true );
+ aDescProps.setProperty( PROP_OutputPosition, CellAddress( aOutputRange.aStart.Tab(), aOutputRange.aStart.Col(), aOutputRange.aStart.Row() ) );
+ }
+
+ /* Properties of the database range (must be set after
+ modifying properties of the filter descriptor,
+ otherwise the 'FilterCriteriaSource' property gets
+ deleted). */
+ PropertySet aRangeProps( xDatabaseRange );
+ aRangeProps.setProperty( PROP_AutoFilter, false );
+ aRangeProps.setProperty( PROP_FilterCriteriaSource,
+ CellRangeAddress( aCriteriaRange.aStart.Tab(),
+ aCriteriaRange.aStart.Col(), aCriteriaRange.aStart.Row(),
+ aCriteriaRange.aEnd.Col(), aCriteriaRange.aEnd.Row() ));
+}
+
+bool AutoFilterBuffer::finalizeImport( const Reference< XDatabaseRange >& rxDatabaseRange, sal_Int16 nSheet )
+{
+ AutoFilter* pAutoFilter = getActiveAutoFilter();
+ if( pAutoFilter && rxDatabaseRange.is() ) try
+ {
+ // the property 'AutoFilter' enables the drop-down buttons
+ PropertySet aRangeProps( rxDatabaseRange );
+ aRangeProps.setProperty( PROP_AutoFilter, true );
+
+ pAutoFilter->finalizeImport( rxDatabaseRange, nSheet );
+
+ // return true to indicate enabled autofilter
+ return true;
+ }
+ catch( Exception& )
+ {
+ }
+ return false;
+}
+
+AutoFilter* AutoFilterBuffer::getActiveAutoFilter()
+{
+ // Excel expects not more than one auto filter per sheet or table
+ OSL_ENSURE( maAutoFilters.size() <= 1, "AutoFilterBuffer::getActiveAutoFilter - too many auto filters" );
+ // stick to the last imported auto filter
+ return maAutoFilters.empty() ? nullptr : maAutoFilters.back().get();
+}
+
+} // namespace oox
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */