1 /*************************************************************************
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
5 * Copyright 2008 by Sun Microsystems, Inc.
7 * OpenOffice.org - a multi-platform office productivity suite
9 * $RCSfile: autofiltercontext.cxx,v $
10 * $Revision: 1.5.4.1 $
12 * This file is part of OpenOffice.org.
14 * OpenOffice.org is free software: you can redistribute it and/or modify
15 * it under the terms of the GNU Lesser General Public License version 3
16 * only, as published by the Free Software Foundation.
18 * OpenOffice.org is distributed in the hope that it will be useful,
19 * but WITHOUT ANY WARRANTY; without even the implied warranty of
20 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 * GNU Lesser General Public License version 3 for more details
22 * (a copy is included in the LICENSE file that accompanied this code).
24 * You should have received a copy of the GNU Lesser General Public License
25 * version 3 along with OpenOffice.org. If not, see
26 * <http://www.openoffice.org/license.html>
27 * for a copy of the LGPLv3 License.
29 ************************************************************************/
31 #include "oox/xls/autofiltercontext.hxx"
32 #include <rtl/ustrbuf.hxx>
33 #include <com/sun/star/container/XNameAccess.hpp>
34 #include <com/sun/star/container/XNamed.hpp>
35 #include <com/sun/star/table/XCellRange.hpp>
36 #include <com/sun/star/sheet/XDatabaseRange.hpp>
37 #include <com/sun/star/sheet/XDatabaseRanges.hpp>
38 #include <com/sun/star/sheet/XSheetFilterDescriptor.hpp>
39 #include <com/sun/star/sheet/FilterOperator.hpp>
40 #include <com/sun/star/sheet/FilterConnection.hpp>
41 #include <com/sun/star/i18n/XLocaleData.hpp>
42 #include "properties.hxx"
43 #include "oox/helper/attributelist.hxx"
44 #include "oox/helper/propertyset.hxx"
45 #include "oox/core/filterbase.hxx"
46 #include "oox/xls/addressconverter.hxx"
48 #define DEBUG_OOX_AUTOFILTER 0
50 #if USE_SC_MULTI_STRING_FILTER_PATCH
51 #include <com/sun/star/sheet/XExtendedSheetFilterDescriptor.hpp>
52 #include <com/sun/star/sheet/TableFilterFieldNormal.hpp>
53 #include <com/sun/star/sheet/TableFilterFieldMultiString.hpp>
54 using ::com::sun::star::sheet::TableFilterFieldNormal
;
55 using ::com::sun::star::sheet::TableFilterFieldMultiString
;
56 using ::com::sun::star::sheet::XExtendedSheetFilterDescriptor
;
58 #include <com/sun/star/sheet/TableFilterField.hpp>
59 using ::com::sun::star::sheet::TableFilterField
;
62 #if DEBUG_OOX_AUTOFILTER
66 using ::rtl::OUString
;
67 using ::rtl::OUStringBuffer
;
68 using ::com::sun::star::uno::Reference
;
69 using ::com::sun::star::uno::Exception
;
70 using ::com::sun::star::uno::Sequence
;
71 using ::com::sun::star::uno::UNO_QUERY
;
72 using ::com::sun::star::uno::UNO_QUERY_THROW
;
73 using ::com::sun::star::container::XNameAccess
;
74 using ::com::sun::star::container::XNamed
;
75 using ::com::sun::star::table::CellRangeAddress
;
76 using ::com::sun::star::table::XCellRange
;
77 using ::com::sun::star::sheet::XDatabaseRange
;
78 using ::com::sun::star::sheet::XDatabaseRanges
;
79 using ::com::sun::star::sheet::XSheetFilterDescriptor
;
80 using ::com::sun::star::i18n::LocaleDataItem
;
81 using ::com::sun::star::i18n::XLocaleData
;
82 using ::com::sun::star::lang::Locale
;
83 using ::oox::core::ContextHandlerRef
;
88 // ============================================================================
90 FilterFieldItem::FilterFieldItem() :
91 #if USE_SC_MULTI_STRING_FILTER_PATCH
92 mpField(new TableFilterFieldNormal
),
94 mpField(new TableFilterField
),
100 FilterFieldItem::FilterFieldItem(Type eType
) :
103 #if USE_SC_MULTI_STRING_FILTER_PATCH
107 mpField
.reset(new TableFilterFieldMultiString
);
110 mpField
.reset(new TableFilterFieldNormal
);
113 mpField
.reset(new TableFilterFieldNormal
);
116 mpField
.reset(new TableFilterField
);
121 // ============================================================================
123 OoxAutoFilterContext::OoxAutoFilterContext( OoxWorksheetFragmentBase
& rFragment
) :
124 OoxWorksheetContextBase( rFragment
),
125 mbValidAddress( false ),
127 mbShowBlank( false ),
128 mbConnectionAnd( false )
132 // oox.core.ContextHandler2Helper interface -----------------------------------
134 ContextHandlerRef
OoxAutoFilterContext::onCreateContext( sal_Int32 nElement
, const AttributeList
& )
136 switch( getCurrentElement() )
138 case XLS_TOKEN( autoFilter
):
141 case XLS_TOKEN( filterColumn
): return this;
145 case XLS_TOKEN( filterColumn
):
148 case XLS_TOKEN( filters
):
149 case XLS_TOKEN( customFilters
):
150 case XLS_TOKEN( top10
):
151 case XLS_TOKEN( dynamicFilter
): return this;
155 case XLS_TOKEN( filters
):
158 case XLS_TOKEN( filter
): return this;
162 case XLS_TOKEN( customFilters
):
165 case XLS_TOKEN( customFilter
): return this;
172 void OoxAutoFilterContext::onStartElement( const AttributeList
& rAttribs
)
174 switch( getCurrentElement() )
176 case XLS_TOKEN( autoFilter
):
177 importAutoFilter( rAttribs
);
179 case XLS_TOKEN( filterColumn
):
180 if ( mbValidAddress
)
181 importFilterColumn( rAttribs
);
183 case XLS_TOKEN( filters
):
184 if ( mbValidAddress
)
185 importFilters( rAttribs
);
187 case XLS_TOKEN( filter
):
188 if ( mbValidAddress
)
189 importFilter( rAttribs
);
191 case XLS_TOKEN( customFilters
):
192 if ( mbValidAddress
)
193 importCustomFilters( rAttribs
);
195 case XLS_TOKEN( customFilter
):
196 if ( mbValidAddress
)
197 importCustomFilter( rAttribs
);
199 case XLS_TOKEN( top10
):
200 if ( mbValidAddress
)
201 importTop10( rAttribs
);
203 case XLS_TOKEN( dynamicFilter
):
204 if ( mbValidAddress
)
205 importDynamicFilter( rAttribs
);
210 void OoxAutoFilterContext::onEndElement( const OUString
& /*rChars*/ )
212 switch( getCurrentElement() )
214 case XLS_TOKEN( autoFilter
):
218 case XLS_TOKEN( filters
):
224 #if DEBUG_OOX_AUTOFILTER
225 static void lclPrintNormalField(
226 #if USE_SC_MULTI_STRING_FILTER_PATCH
227 TableFilterFieldNormal
* pField
229 TableFilterField
* pField
233 using namespace ::com::sun::star::sheet
;
235 printf(" Operator: ");
236 switch ( pField
->Operator
)
238 case FilterOperator_EQUAL
:
241 case FilterOperator_NOT_EQUAL
:
244 case com::sun::star::sheet::FilterOperator_GREATER
:
247 case com::sun::star::sheet::FilterOperator_GREATER_EQUAL
:
248 printf("GREATER_EQUAL");
250 case FilterOperator_LESS
:
253 case FilterOperator_LESS_EQUAL
:
254 printf("LESS_EQUAL");
256 case FilterOperator_NOT_EMPTY
:
259 case FilterOperator_EMPTY
:
262 case FilterOperator_BOTTOM_PERCENT
:
263 printf("BOTTOM_PERCENT");
265 case FilterOperator_BOTTOM_VALUES
:
266 printf("BOTTOM_VALUES");
268 case FilterOperator_TOP_PERCENT
:
269 printf("TOP_PERCENT");
271 case FilterOperator_TOP_VALUES
:
272 printf("TOP_VALUES");
279 printf(" StringValue: %s\n",
280 OUStringToOString(pField
->StringValue
, RTL_TEXTENCODING_UTF8
).getStr());
282 printf(" NumericValue: %g\n", pField
->NumericValue
);
284 printf(" IsNumeric: ");
285 if (pField
->IsNumeric
)
291 static void lclPrintFieldConnection( ::com::sun::star::sheet::FilterConnection eConn
)
293 using namespace ::com::sun::star::sheet
;
295 printf(" Connection: ");
298 case FilterConnection_AND
:
301 case FilterConnection_OR
:
304 case FilterConnection_MAKE_FIXED_SIZE
:
305 printf("MAKE_FIXED_SIZE");
313 static void lclPrintFilterField( const FilterFieldItem
& aItem
)
315 using namespace ::com::sun::star::sheet
;
317 printf("----------------------------------------\n");
318 #if USE_SC_MULTI_STRING_FILTER_PATCH
320 // Print common fields first.
322 TableFilterFieldBase
* pField
= aItem
.mpField
.get();
323 printf(" Field: %ld\n", pField
->Field
);
324 lclPrintFieldConnection(pField
->Connection
);
326 switch ( aItem
.meType
)
328 case FilterFieldItem::NORMAL
:
330 TableFilterFieldNormal
* pField
= static_cast<TableFilterFieldNormal
*>(aItem
.mpField
.get());
331 lclPrintNormalField(pField
);
334 case FilterFieldItem::MULTI_STRING
:
336 TableFilterFieldMultiString
* pMultiStrField
= static_cast<TableFilterFieldMultiString
*>(aItem
.mpField
.get());
337 sal_Int32 nSize
= pMultiStrField
->StringSet
.getLength();
338 printf(" StringSet:\n");
339 for ( sal_Int32 i
= 0; i
< nSize
; ++i
)
342 OUStringToOString(pMultiStrField
->StringSet
[i
], RTL_TEXTENCODING_UTF8
).getStr());
348 TableFilterField
* pField
= aItem
.mpField
.get();
349 printf(" Field: %ld\n", pField
->Field
);
350 lclPrintFieldConnection(pField
->Connection
);
351 lclPrintNormalField(pField
);
358 void OoxAutoFilterContext::initialize()
361 maFilterNames
.clear();
362 mbValidAddress
= mbShowBlank
= mbUseRegex
= mbConnectionAnd
= false;
365 void OoxAutoFilterContext::setAutoFilter()
367 using namespace ::com::sun::star::sheet
;
369 // Name this built-in database.
370 OUStringBuffer
sDataAreaNameBuf( CREATE_OUSTRING("Excel_BuiltIn__FilterDatabase_ ") );
371 sDataAreaNameBuf
.append( static_cast<sal_Int32
>(getSheetIndex()+1) );
373 OUString sDataAreaName
= sDataAreaNameBuf
.makeStringAndClear();
374 Reference
< XCellRange
> xCellRange
= getCellRange( maAutoFilterRange
);
376 // Create a new database range, add filters to it and refresh the database
377 // for that to take effect.
379 Reference
< XDatabaseRanges
> xDBRanges
= getDatabaseRanges();
380 if ( !xDBRanges
.is() )
382 OSL_ENSURE( false, "OoxAutoFilterContext::setAutoFilter: DBRange empty" );
386 Reference
< XNameAccess
> xNA( xDBRanges
, UNO_QUERY_THROW
);
387 if ( !xNA
->hasByName( sDataAreaName
) )
388 xDBRanges
->addNewByName( sDataAreaName
, maAutoFilterRange
);
390 Reference
< XDatabaseRange
> xDB( xNA
->getByName( sDataAreaName
), UNO_QUERY
);
393 PropertySet
aProp( xDB
);
394 aProp
.setProperty( PROP_AutoFilter
, true );
397 sal_Int32 nSize
= maFields
.size();
398 sal_Int32 nMaxFieldCount
= nSize
;
399 Reference
< XSheetFilterDescriptor
> xDescriptor
= xDB
->getFilterDescriptor();
400 if ( xDescriptor
.is() )
402 PropertySet
aProp( xDescriptor
);
403 aProp
.setProperty( PROP_ContainsHeader
, true );
404 aProp
.setProperty( PROP_UseRegularExpressions
, mbUseRegex
);
405 aProp
.getProperty( nMaxFieldCount
, PROP_MaxFieldCount
);
409 OSL_ENSURE(false, "OoxAutoFilterContext::setAutoFilter: descriptor is empty");
413 // Unpack all column field items into a sequence.
414 #if USE_SC_MULTI_STRING_FILTER_PATCH
415 Reference
< XExtendedSheetFilterDescriptor
> xExtDescriptor( xDescriptor
, UNO_QUERY
);
416 if ( !xExtDescriptor
.is() )
418 OSL_ENSURE(false, "OoxAutoFilterContext::setAutoFilter: extended descriptor is empty");
422 xExtDescriptor
->begin();
424 ::std::list
< FilterFieldItem
>::const_iterator itr
= maFields
.begin(), itrEnd
= maFields
.end();
425 for (sal_Int32 i
= 0; itr
!= itrEnd
&& i
< nMaxFieldCount
; ++itr
, ++i
)
427 #if DEBUG_OOX_AUTOFILTER
428 lclPrintFilterField(*itr
);
430 switch ( itr
->meType
)
432 case oox::xls::FilterFieldItem::MULTI_STRING
:
434 // multi-string filter type
435 TableFilterFieldMultiString
* pField
= static_cast<TableFilterFieldMultiString
*>( itr
->mpField
.get() );
436 xExtDescriptor
->addFilterFieldMultiString( *pField
);
439 case oox::xls::FilterFieldItem::NORMAL
:
441 // normal filter type
442 TableFilterFieldNormal
* pField
= static_cast<TableFilterFieldNormal
*>( itr
->mpField
.get() );
443 xExtDescriptor
->addFilterFieldNormal( *pField
);
446 xExtDescriptor
->commit();
449 Sequence
< TableFilterField
> aFields(nSize
);
450 ::std::list
< FilterFieldItem
>::const_iterator itr
= maFields
.begin(), itrEnd
= maFields
.end();
451 for (sal_Int32 i
= 0; itr
!= itrEnd
&& i
< nMaxFieldCount
; ++itr
, ++i
)
453 #if DEBUG_OOX_AUTOFILTER
454 lclPrintFilterField( *itr
);
456 aFields
[i
] = *itr
->mpField
;
458 xDescriptor
->setFilterFields( aFields
);
463 void OoxAutoFilterContext::maybeShowBlank()
465 using namespace ::com::sun::star::sheet
;
470 #if USE_SC_MULTI_STRING_FILTER_PATCH
471 FilterFieldItem
aItem(FilterFieldItem::NORMAL
);
472 TableFilterFieldNormal
* pField
= static_cast<TableFilterFieldNormal
*>(aItem
.mpField
.get());
473 pField
->Field
= mnCurColID
;
474 pField
->Operator
= FilterOperator_EMPTY
;
475 pField
->Connection
= FilterConnection_AND
;
476 pField
->IsNumeric
= false;
478 FilterFieldItem aItem
;
479 aItem
.mpField
->Field
= mnCurColID
;
480 aItem
.mpField
->Operator
= FilterOperator_EMPTY
;
481 aItem
.mpField
->Connection
= FilterConnection_AND
;
482 aItem
.mpField
->IsNumeric
= false;
484 maFields
.push_back(aItem
);
487 void OoxAutoFilterContext::setFilterNames()
489 using namespace ::com::sun::star::sheet
;
492 sal_Int32 size
= maFilterNames
.size();
496 #if USE_SC_MULTI_STRING_FILTER_PATCH
497 Sequence
< OUString
> aStrList(size
);
498 ::std::list
< OUString
>::const_iterator itr
= maFilterNames
.begin(), itrEnd
= maFilterNames
.end();
499 for (sal_Int32 i
= 0; itr
!= itrEnd
; ++itr
, ++i
)
502 FilterFieldItem
aItem(FilterFieldItem::MULTI_STRING
);
503 TableFilterFieldMultiString
* pField
= static_cast<TableFilterFieldMultiString
*>( aItem
.mpField
.get() );
504 pField
->Field
= mnCurColID
;
505 pField
->Connection
= FilterConnection_AND
;
506 pField
->StringSet
= aStrList
;
508 maFields
.push_back(aItem
);
510 static const OUString sSep
= CREATE_OUSTRING("|");
515 buf
.append( CREATE_OUSTRING("^(") );
519 ::std::list
< OUString
>::const_iterator itr
= maFilterNames
.begin(), itrEnd
= maFilterNames
.end();
521 for (; itr
!= itrEnd
; ++itr
)
530 buf
.append( CREATE_OUSTRING(")$") );
532 FilterFieldItem aItem
;
533 aItem
.mpField
->Field
= mnCurColID
;
534 aItem
.mpField
->StringValue
= buf
.makeStringAndClear();
535 aItem
.mpField
->Operator
= FilterOperator_EQUAL
;
536 aItem
.mpField
->Connection
= FilterConnection_AND
;
537 aItem
.mpField
->IsNumeric
= false;
538 maFields
.push_back(aItem
);
542 void OoxAutoFilterContext::importAutoFilter( const AttributeList
& rAttribs
)
546 mbValidAddress
= getAddressConverter().convertToCellRange(
547 maAutoFilterRange
, rAttribs
.getString( XML_ref
, OUString() ), getSheetIndex(), true, true );
550 void OoxAutoFilterContext::importFilterColumn( const AttributeList
& rAttribs
)
552 // hiddenButton and showButton attributes are not used for now.
553 mnCurColID
= rAttribs
.getInteger( XML_colId
, -1 );
556 void OoxAutoFilterContext::importTop10( const AttributeList
& rAttribs
)
558 using namespace ::com::sun::star::sheet
;
560 // filterVal attribute is not necessarily, since Calc also supports top 10
561 // and top 10% filter type.
562 FilterFieldItem aItem
;
563 #if USE_SC_MULTI_STRING_FILTER_PATCH
564 TableFilterFieldNormal
* pField
= static_cast<TableFilterFieldNormal
*>(aItem
.mpField
.get());
566 TableFilterField
* pField
= aItem
.mpField
.get();
568 pField
->Field
= mnCurColID
;
570 bool bPercent
= rAttribs
.getBool( XML_percent
, false );
571 bool bTop
= rAttribs
.getBool( XML_top
, true );
572 pField
->NumericValue
= rAttribs
.getDouble( XML_val
, 0.0 );
573 pField
->IsNumeric
= true;
575 // When top10 filter item is present, that's the only filter item for that column.
578 pField
->Operator
= FilterOperator_TOP_PERCENT
;
580 pField
->Operator
= FilterOperator_TOP_VALUES
;
583 pField
->Operator
= FilterOperator_BOTTOM_PERCENT
;
585 pField
->Operator
= FilterOperator_BOTTOM_VALUES
;
587 maFields
.push_back(aItem
);
590 void OoxAutoFilterContext::importCustomFilters( const AttributeList
& rAttribs
)
592 // OR is default when the 'and' attribute is absent.
593 mbConnectionAnd
= rAttribs
.getBool( XML_and
, false );
596 /** Do a best-effort guess of whether or not the given string is numerical. */
597 static bool lclIsNumeric( const OUString
& _str
, const LocaleDataItem
& aLocaleItem
)
599 OUString str
= _str
.trim();
600 sal_Int32 size
= str
.getLength();
603 // Empty string. This can't be a number.
606 // Get the decimal separator for the current locale.
607 const OUString
& sep
= aLocaleItem
.decimalSeparator
;
609 bool bDecimalSep
= false;
610 for (sal_Int32 i
= 0; i
< size
; ++i
)
612 OUString c
= str
.copy(i
, 1);
613 if ( !c
.compareTo(sep
) )
623 if ( (0 > c
.compareToAscii("0") || 0 < c
.compareToAscii("9")) )
630 /** Convert wildcard characters to regex equivalent. Returns true if any
631 wildcard character is found. */
632 static bool lclWildcard2Regex( OUString
& str
)
634 bool bWCFound
= false;
636 sal_Int32 size
= str
.getLength();
637 buf
.ensureCapacity(size
+ 6); // pure heuristics.
639 sal_Unicode dot
= '.', star
= '*', hat
= '^', dollar
= '$';
641 for (sal_Int32 i
= 0; i
< size
; ++i
)
643 OUString c
= str
.copy(i
, 1);
644 if ( !c
.compareToAscii("?") )
649 else if ( !c
.compareToAscii("*") )
661 str
= buf
.makeStringAndClear();
666 /** Translate Excel's filter operator to Calc's. */
667 static ::com::sun::star::sheet::FilterOperator
lclTranslateFilterOp( sal_Int32 nToken
)
669 using namespace ::com::sun::star::sheet
;
674 return FilterOperator_EQUAL
;
676 return FilterOperator_NOT_EQUAL
;
677 case XML_greaterThan
:
678 return FilterOperator_GREATER
;
679 case XML_greaterThanOrEqual
:
680 return FilterOperator_GREATER_EQUAL
;
682 return FilterOperator_LESS
;
683 case XML_lessThanOrEqual
:
684 return FilterOperator_LESS_EQUAL
;
686 return FilterOperator_EQUAL
;
689 void OoxAutoFilterContext::importCustomFilter( const AttributeList
& rAttribs
)
691 using namespace ::com::sun::star::sheet
;
693 sal_Int32 nToken
= rAttribs
.getToken( XML_operator
, XML_equal
);
694 #if USE_SC_MULTI_STRING_FILTER_PATCH
695 FilterFieldItem
aItem(FilterFieldItem::NORMAL
);
696 TableFilterFieldNormal
* pField
= static_cast<TableFilterFieldNormal
*>(aItem
.mpField
.get());
698 FilterFieldItem aItem
;
699 TableFilterField
* pField
= aItem
.mpField
.get();
701 pField
->Field
= mnCurColID
;
702 pField
->StringValue
= rAttribs
.getString( XML_val
, OUString() );
703 pField
->NumericValue
= pField
->StringValue
.toDouble();
704 pField
->Operator
= lclTranslateFilterOp( nToken
);
706 if ( nToken
== XML_notEqual
&& !pField
->StringValue
.compareToAscii(" ") )
708 // Special case for hiding blanks. Excel translates "hide blanks" to
709 // (filter if notEqual " "). So, we need to translate it back.
710 pField
->Operator
= FilterOperator_NOT_EMPTY
;
711 pField
->IsNumeric
= false;
712 maFields
.push_back(aItem
);
721 Reference
< XLocaleData
> xLocale( getGlobalFactory()->createInstance(
722 CREATE_OUSTRING("com.sun.star.i18n.LocaleData") ), UNO_QUERY
);
727 LocaleDataItem aLocaleItem
= xLocale
->getLocaleItem( ::com::sun::star::lang::Locale() );
728 pField
->IsNumeric
= lclIsNumeric(pField
->StringValue
, aLocaleItem
);
730 if ( !pField
->IsNumeric
&& lclWildcard2Regex(pField
->StringValue
) )
733 maFields
.push_back(aItem
);
737 case XML_greaterThan
:
738 case XML_greaterThanOrEqual
:
740 case XML_lessThanOrEqual
:
742 pField
->IsNumeric
= true;
743 maFields
.push_back(aItem
);
747 OSL_ENSURE( false, "OoxAutoFilterContext::importCustomFilter: unhandled case" );
751 void OoxAutoFilterContext::importFilters( const AttributeList
& rAttribs
)
753 // blank (boolean) and calendarType attributes can be present, but not used for now.
755 mbShowBlank
= rAttribs
.getBool( XML_blank
, false );
756 maFilterNames
.clear();
759 void OoxAutoFilterContext::importFilter( const AttributeList
& rAttribs
)
761 if (mnCurColID
== -1)
764 OUString value
= rAttribs
.getString( XML_val
, OUString() );
765 if ( value
.getLength() )
766 maFilterNames
.push_back(value
);
769 void OoxAutoFilterContext::importDynamicFilter( const AttributeList
& /*rAttribs*/ )
771 // not implemented yet - Calc doesn't support this.
774 // ============================================================================