use insert function instead of for loop
[LibreOffice.git] / sc / source / filter / excel / xipivot.cxx
blobfb14ec4d07ebb9327cd418b8b917f77ecb5542d7
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
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 <xipivot.hxx>
22 #include <com/sun/star/sheet/DataPilotFieldSortInfo.hpp>
23 #include <com/sun/star/sheet/DataPilotFieldAutoShowInfo.hpp>
24 #include <com/sun/star/sheet/DataPilotFieldLayoutInfo.hpp>
25 #include <com/sun/star/sheet/DataPilotFieldReference.hpp>
26 #include <com/sun/star/sheet/DataPilotFieldReferenceItemType.hpp>
28 #include <tools/datetime.hxx>
29 #include <svl/intitem.hxx>
30 #include <svl/numformat.hxx>
31 #include <sal/log.hxx>
32 #include <sot/storage.hxx>
33 #include <comphelper/configuration.hxx>
35 #include <document.hxx>
36 #include <formulacell.hxx>
37 #include <dpsave.hxx>
38 #include <dpdimsave.hxx>
39 #include <dpobject.hxx>
40 #include <dpshttab.hxx>
41 #include <dpoutputgeometry.hxx>
42 #include <scitems.hxx>
43 #include <attrib.hxx>
45 #include <xltracer.hxx>
46 #include <xistream.hxx>
47 #include <xihelper.hxx>
48 #include <xilink.hxx>
49 #include <xiescher.hxx>
51 //TODO ExcelToSc usage
52 #include <excform.hxx>
53 #include <documentimport.hxx>
55 #include <vector>
57 using namespace com::sun::star;
59 using ::com::sun::star::sheet::DataPilotFieldOrientation_DATA;
60 using ::com::sun::star::sheet::DataPilotFieldSortInfo;
61 using ::com::sun::star::sheet::DataPilotFieldAutoShowInfo;
62 using ::com::sun::star::sheet::DataPilotFieldLayoutInfo;
63 using ::com::sun::star::sheet::DataPilotFieldReference;
64 using ::std::vector;
66 // Pivot cache
68 XclImpPCItem::XclImpPCItem( XclImpStream& rStrm )
70 switch( rStrm.GetRecId() )
72 case EXC_ID_SXDOUBLE: ReadSxdouble( rStrm ); break;
73 case EXC_ID_SXBOOLEAN: ReadSxboolean( rStrm ); break;
74 case EXC_ID_SXERROR: ReadSxerror( rStrm ); break;
75 case EXC_ID_SXINTEGER: ReadSxinteger( rStrm ); break;
76 case EXC_ID_SXSTRING: ReadSxstring( rStrm ); break;
77 case EXC_ID_SXDATETIME: ReadSxdatetime( rStrm ); break;
78 case EXC_ID_SXEMPTY: ReadSxempty( rStrm ); break;
79 default: OSL_FAIL( "XclImpPCItem::XclImpPCItem - unknown record id" );
83 namespace {
85 void lclSetValue( XclImpRoot& rRoot, const ScAddress& rScPos, double fValue, SvNumFormatType nFormatType )
87 ScDocumentImport& rDoc = rRoot.GetDocImport();
88 rDoc.setNumericCell(rScPos, fValue);
89 sal_uInt32 nScNumFmt = rRoot.GetFormatter().GetStandardFormat( nFormatType, rRoot.GetDocLanguage() );
90 rDoc.getDoc().ApplyAttr(
91 rScPos.Col(), rScPos.Row(), rScPos.Tab(), SfxUInt32Item(ATTR_VALUE_FORMAT, nScNumFmt));
94 } // namespace
96 void XclImpPCItem::WriteToSource( XclImpRoot& rRoot, const ScAddress& rScPos ) const
98 ScDocumentImport& rDoc = rRoot.GetDocImport();
99 if( const OUString* pText = GetText() )
100 rDoc.setStringCell(rScPos, *pText);
101 else if( const double* pfValue = GetDouble() )
102 rDoc.setNumericCell(rScPos, *pfValue);
103 else if( const sal_Int16* pnValue = GetInteger() )
104 rDoc.setNumericCell(rScPos, *pnValue);
105 else if( const bool* pbValue = GetBool() )
106 lclSetValue( rRoot, rScPos, *pbValue ? 1.0 : 0.0, SvNumFormatType::LOGICAL );
107 else if( const DateTime* pDateTime = GetDateTime() )
109 // set number format date, time, or date/time, depending on the value
110 double fValue = rRoot.GetDoubleFromDateTime( *pDateTime );
111 double fInt = 0.0;
112 double fFrac = modf( fValue, &fInt );
113 SvNumFormatType nFormatType = ((fFrac == 0.0) && (fInt != 0.0)) ? SvNumFormatType::DATE :
114 ((fInt == 0.0) ? SvNumFormatType::TIME : SvNumFormatType::DATETIME);
115 lclSetValue( rRoot, rScPos, fValue, nFormatType );
117 else if( const sal_uInt16* pnError = GetError() )
119 double fValue;
120 sal_uInt8 nErrCode = static_cast< sal_uInt8 >( *pnError );
121 std::unique_ptr<ScTokenArray> pScTokArr = rRoot.GetOldFmlaConverter().GetBoolErr(
122 XclTools::ErrorToEnum( fValue, true, nErrCode ) );
123 ScFormulaCell* pCell = pScTokArr
124 ? new ScFormulaCell(rDoc.getDoc(), rScPos, std::move(pScTokArr))
125 : new ScFormulaCell(rDoc.getDoc(), rScPos);
126 pCell->SetHybridDouble( fValue );
127 rDoc.setFormulaCell(rScPos, pCell);
131 void XclImpPCItem::ReadSxdouble( XclImpStream& rStrm )
133 OSL_ENSURE( rStrm.GetRecSize() == 8, "XclImpPCItem::ReadSxdouble - wrong record size" );
134 SetDouble( rStrm.ReadDouble() );
137 void XclImpPCItem::ReadSxboolean( XclImpStream& rStrm )
139 OSL_ENSURE( rStrm.GetRecSize() == 2, "XclImpPCItem::ReadSxboolean - wrong record size" );
140 SetBool( rStrm.ReaduInt16() != 0 );
143 void XclImpPCItem::ReadSxerror( XclImpStream& rStrm )
145 OSL_ENSURE( rStrm.GetRecSize() == 2, "XclImpPCItem::ReadSxerror - wrong record size" );
146 SetError( rStrm.ReaduInt16() );
149 void XclImpPCItem::ReadSxinteger( XclImpStream& rStrm )
151 OSL_ENSURE( rStrm.GetRecSize() == 2, "XclImpPCItem::ReadSxinteger - wrong record size" );
152 SetInteger( rStrm.ReadInt16() );
155 void XclImpPCItem::ReadSxstring( XclImpStream& rStrm )
157 OSL_ENSURE( rStrm.GetRecSize() >= 3, "XclImpPCItem::ReadSxstring - wrong record size" );
158 SetText( rStrm.ReadUniString() );
161 void XclImpPCItem::ReadSxdatetime( XclImpStream& rStrm )
163 OSL_ENSURE( rStrm.GetRecSize() == 8, "XclImpPCItem::ReadSxdatetime - wrong record size" );
164 sal_uInt16 nYear, nMonth;
165 sal_uInt8 nDay, nHour, nMin, nSec;
166 nYear = rStrm.ReaduInt16();
167 nMonth = rStrm.ReaduInt16();
168 nDay = rStrm.ReaduInt8();
169 nHour = rStrm.ReaduInt8();
170 nMin = rStrm.ReaduInt8();
171 nSec = rStrm.ReaduInt8();
172 SetDateTime( DateTime( Date( nDay, nMonth, nYear ), tools::Time( nHour, nMin, nSec ) ) );
175 void XclImpPCItem::ReadSxempty( XclImpStream& rStrm )
177 OSL_ENSURE( rStrm.GetRecSize() == 0, "XclImpPCItem::ReadSxempty - wrong record size" );
178 SetEmpty();
181 XclImpPCField::XclImpPCField( const XclImpRoot& rRoot, XclImpPivotCache& rPCache, sal_uInt16 nFieldIdx ) :
182 XclPCField( EXC_PCFIELD_UNKNOWN, nFieldIdx ),
183 XclImpRoot( rRoot ),
184 mrPCache( rPCache ),
185 mnSourceScCol( -1 ),
186 mbNumGroupInfoRead( false )
190 XclImpPCField::~XclImpPCField()
194 // general field/item access --------------------------------------------------
196 const OUString& XclImpPCField::GetFieldName( const ScfStringVec& rVisNames ) const
198 if( IsGroupChildField() && (mnFieldIdx < rVisNames.size()) )
200 const OUString& rVisName = rVisNames[ mnFieldIdx ];
201 if (!rVisName.isEmpty())
202 return rVisName;
204 return maFieldInfo.maName;
207 const XclImpPCField* XclImpPCField::GetGroupBaseField() const
209 OSL_ENSURE( IsGroupChildField(), "XclImpPCField::GetGroupBaseField - this field type does not have a base field" );
210 return IsGroupChildField() ? mrPCache.GetField( maFieldInfo.mnGroupBase ) : nullptr;
213 const XclImpPCItem* XclImpPCField::GetItem( sal_uInt16 nItemIdx ) const
215 return (nItemIdx < maItems.size()) ? maItems[ nItemIdx ].get() : nullptr;
218 const XclImpPCItem* XclImpPCField::GetLimitItem( sal_uInt16 nItemIdx ) const
220 OSL_ENSURE( nItemIdx < 3, "XclImpPCField::GetLimitItem - invalid item index" );
221 OSL_ENSURE( nItemIdx < maNumGroupItems.size(), "XclImpPCField::GetLimitItem - no item found" );
222 return (nItemIdx < maNumGroupItems.size()) ? maNumGroupItems[ nItemIdx ].get() : nullptr;
225 void XclImpPCField::WriteFieldNameToSource( SCCOL nScCol, SCTAB nScTab )
227 OSL_ENSURE( HasOrigItems(), "XclImpPCField::WriteFieldNameToSource - only for standard fields" );
228 GetDocImport().setStringCell(ScAddress(nScCol, 0, nScTab), maFieldInfo.maName);
229 mnSourceScCol = nScCol;
232 void XclImpPCField::WriteOrigItemToSource( SCROW nScRow, SCTAB nScTab, sal_uInt16 nItemIdx )
234 if( nItemIdx < maOrigItems.size() )
235 maOrigItems[ nItemIdx ]->WriteToSource( GetRoot(), ScAddress( mnSourceScCol, nScRow, nScTab ) );
238 void XclImpPCField::WriteLastOrigItemToSource( SCROW nScRow, SCTAB nScTab )
240 if( !maOrigItems.empty() )
241 maOrigItems.back()->WriteToSource( GetRoot(), ScAddress( mnSourceScCol, nScRow, nScTab ) );
244 // records --------------------------------------------------------------------
246 void XclImpPCField::ReadSxfield( XclImpStream& rStrm )
248 rStrm >> maFieldInfo;
250 /* Detect the type of this field. This is done very restrictive to detect
251 any unexpected state. */
252 meFieldType = EXC_PCFIELD_UNKNOWN;
254 bool bItems = ::get_flag( maFieldInfo.mnFlags, EXC_SXFIELD_HASITEMS );
255 bool bPostp = ::get_flag( maFieldInfo.mnFlags, EXC_SXFIELD_POSTPONE );
256 bool bCalced = ::get_flag( maFieldInfo.mnFlags, EXC_SXFIELD_CALCED );
257 bool bChild = ::get_flag( maFieldInfo.mnFlags, EXC_SXFIELD_HASCHILD );
258 bool bNum = ::get_flag( maFieldInfo.mnFlags, EXC_SXFIELD_NUMGROUP );
260 sal_uInt16 nVisC = maFieldInfo.mnVisItems;
261 sal_uInt16 nGroupC = maFieldInfo.mnGroupItems;
262 sal_uInt16 nBaseC = maFieldInfo.mnBaseItems;
263 sal_uInt16 nOrigC = maFieldInfo.mnOrigItems;
264 OSL_ENSURE( nVisC > 0, "XclImpPCField::ReadSxfield - field without visible items" );
266 sal_uInt16 nType = maFieldInfo.mnFlags & EXC_SXFIELD_DATA_MASK;
267 bool bType =
268 (nType == EXC_SXFIELD_DATA_STR) ||
269 (nType == EXC_SXFIELD_DATA_INT) ||
270 (nType == EXC_SXFIELD_DATA_DBL) ||
271 (nType == EXC_SXFIELD_DATA_STR_INT) ||
272 (nType == EXC_SXFIELD_DATA_STR_DBL) ||
273 (nType == EXC_SXFIELD_DATA_DATE) ||
274 (nType == EXC_SXFIELD_DATA_DATE_EMP) ||
275 (nType == EXC_SXFIELD_DATA_DATE_NUM) ||
276 (nType == EXC_SXFIELD_DATA_DATE_STR);
277 bool bTypeNone =
278 (nType == EXC_SXFIELD_DATA_NONE);
279 // for now, ignore data type of calculated fields
280 OSL_ENSURE( bCalced || bType || bTypeNone, "XclImpPCField::ReadSxfield - unknown item data type" );
282 if( !(nVisC > 0 || bPostp) )
283 return;
285 if( bItems && !bPostp )
287 if( !bCalced )
289 // 1) standard fields and standard grouping fields
290 if( !bNum )
292 // 1a) standard field without grouping
293 if( bType && (nGroupC == 0) && (nBaseC == 0) && (nOrigC == nVisC) )
294 meFieldType = EXC_PCFIELD_STANDARD;
296 // 1b) standard grouping field
297 else if( bTypeNone && (nGroupC == nVisC) && (nBaseC > 0) && (nOrigC == 0) )
298 meFieldType = EXC_PCFIELD_STDGROUP;
300 // 2) numerical grouping fields
301 else if( (nGroupC == nVisC) && (nBaseC == 0) )
303 // 2a) single num/date grouping field without child grouping field
304 if( !bChild && bType && (nOrigC > 0) )
306 switch( nType )
308 case EXC_SXFIELD_DATA_INT:
309 case EXC_SXFIELD_DATA_DBL: meFieldType = EXC_PCFIELD_NUMGROUP; break;
310 case EXC_SXFIELD_DATA_DATE: meFieldType = EXC_PCFIELD_DATEGROUP; break;
311 default: OSL_FAIL( "XclImpPCField::ReadSxfield - numeric group with wrong data type" );
315 // 2b) first date grouping field with child grouping field
316 else if( bChild && (nType == EXC_SXFIELD_DATA_DATE) && (nOrigC > 0) )
317 meFieldType = EXC_PCFIELD_DATEGROUP;
319 // 2c) additional date grouping field
320 else if( bTypeNone && (nOrigC == 0) )
321 meFieldType = EXC_PCFIELD_DATECHILD;
323 OSL_ENSURE( meFieldType != EXC_PCFIELD_UNKNOWN, "XclImpPCField::ReadSxfield - invalid standard or grouped field" );
326 // 3) calculated field
327 else
329 if( !bChild && !bNum && (nGroupC == 0) && (nBaseC == 0) && (nOrigC == 0) )
330 meFieldType = EXC_PCFIELD_CALCED;
331 OSL_ENSURE( meFieldType == EXC_PCFIELD_CALCED, "XclImpPCField::ReadSxfield - invalid calculated field" );
335 else if( !bItems && bPostp )
337 // 4) standard field with postponed items
338 if( !bCalced && !bChild && !bNum && bType && (nGroupC == 0) && (nBaseC == 0) && (nOrigC == 0) )
339 meFieldType = EXC_PCFIELD_STANDARD;
340 OSL_ENSURE( meFieldType == EXC_PCFIELD_STANDARD, "XclImpPCField::ReadSxfield - invalid postponed field" );
344 void XclImpPCField::ReadItem( XclImpStream& rStrm )
346 OSL_ENSURE( HasInlineItems() || HasPostponedItems(), "XclImpPCField::ReadItem - field does not expect items" );
348 // read the item
349 XclImpPCItemRef xItem = std::make_shared<XclImpPCItem>( rStrm );
351 // try to insert into an item list
352 if( mbNumGroupInfoRead )
354 // there are 3 items after SXNUMGROUP that contain grouping limits and step count
355 if( maNumGroupItems.size() < 3 )
356 maNumGroupItems.push_back( xItem );
357 else
358 maOrigItems.push_back( xItem );
360 else if( HasInlineItems() || HasPostponedItems() )
362 maItems.push_back( xItem );
363 // visible item is original item in standard fields
364 if( IsStandardField() )
365 maOrigItems.push_back( xItem );
369 void XclImpPCField::ReadSxnumgroup( XclImpStream& rStrm )
371 OSL_ENSURE( IsNumGroupField() || IsDateGroupField(), "XclImpPCField::ReadSxnumgroup - SXNUMGROUP outside numeric grouping field" );
372 OSL_ENSURE( !mbNumGroupInfoRead, "XclImpPCField::ReadSxnumgroup - multiple SXNUMGROUP records" );
373 OSL_ENSURE( maItems.size() == maFieldInfo.mnGroupItems, "XclImpPCField::ReadSxnumgroup - SXNUMGROUP out of record order" );
374 rStrm >> maNumGroupInfo;
375 mbNumGroupInfoRead = IsNumGroupField() || IsDateGroupField();
378 void XclImpPCField::ReadSxgroupinfo( XclImpStream& rStrm )
380 OSL_ENSURE( IsStdGroupField(), "XclImpPCField::ReadSxgroupinfo - SXGROUPINFO outside grouping field" );
381 OSL_ENSURE( maGroupOrder.empty(), "XclImpPCField::ReadSxgroupinfo - multiple SXGROUPINFO records" );
382 OSL_ENSURE( maItems.size() == maFieldInfo.mnGroupItems, "XclImpPCField::ReadSxgroupinfo - SXGROUPINFO out of record order" );
383 OSL_ENSURE( (rStrm.GetRecLeft() / 2) == maFieldInfo.mnBaseItems, "XclImpPCField::ReadSxgroupinfo - wrong SXGROUPINFO size" );
384 maGroupOrder.clear();
385 size_t nSize = rStrm.GetRecLeft() / 2;
386 maGroupOrder.resize( nSize, 0 );
387 for( size_t nIdx = 0; nIdx < nSize; ++nIdx )
388 maGroupOrder[ nIdx ] = rStrm.ReaduInt16();
391 // grouping -------------------------------------------------------------------
393 void XclImpPCField::ConvertGroupField( ScDPSaveData& rSaveData, const ScfStringVec& rVisNames ) const
395 if (!GetFieldName(rVisNames).isEmpty())
397 if( IsStdGroupField() )
398 ConvertStdGroupField( rSaveData, rVisNames );
399 else if( IsNumGroupField() )
400 ConvertNumGroupField( rSaveData, rVisNames );
401 else if( IsDateGroupField() )
402 ConvertDateGroupField( rSaveData, rVisNames );
406 // private --------------------------------------------------------------------
408 void XclImpPCField::ConvertStdGroupField( ScDPSaveData& rSaveData, const ScfStringVec& rVisNames ) const
410 const XclImpPCField* pBaseField = GetGroupBaseField();
411 if(!pBaseField)
412 return;
414 const OUString& rBaseFieldName = pBaseField->GetFieldName( rVisNames );
415 if( rBaseFieldName.isEmpty() )
416 return;
418 // *** create a ScDPSaveGroupItem for each own item, they collect base item names ***
419 ScDPSaveGroupItemVec aGroupItems;
420 aGroupItems.reserve( maItems.size() );
421 // initialize with own item names
422 for( const auto& rxItem : maItems )
423 aGroupItems.emplace_back( rxItem->ConvertToText() );
425 // *** iterate over all base items, set their names at corresponding own items ***
426 for( sal_uInt16 nItemIdx = 0, nItemCount = static_cast< sal_uInt16 >( maGroupOrder.size() ); nItemIdx < nItemCount; ++nItemIdx )
427 if( maGroupOrder[ nItemIdx ] < aGroupItems.size() )
428 if( const XclImpPCItem* pBaseItem = pBaseField->GetItem( nItemIdx ) )
429 if( const XclImpPCItem* pGroupItem = GetItem( maGroupOrder[ nItemIdx ] ) )
430 if( *pBaseItem != *pGroupItem )
431 aGroupItems[ maGroupOrder[ nItemIdx ] ].AddElement( pBaseItem->ConvertToText() );
433 // *** create the ScDPSaveGroupDimension object, fill with grouping info ***
434 ScDPSaveGroupDimension aGroupDim( rBaseFieldName, GetFieldName( rVisNames ) );
435 for( const auto& rGroupItem : aGroupItems )
436 if( !rGroupItem.IsEmpty() )
437 aGroupDim.AddGroupItem( rGroupItem );
438 rSaveData.GetDimensionData()->AddGroupDimension( aGroupDim );
441 void XclImpPCField::ConvertNumGroupField( ScDPSaveData& rSaveData, const ScfStringVec& rVisNames ) const
443 ScDPNumGroupInfo aNumInfo( GetScNumGroupInfo() );
444 ScDPSaveNumGroupDimension aNumGroupDim( GetFieldName( rVisNames ), aNumInfo );
445 rSaveData.GetDimensionData()->AddNumGroupDimension( aNumGroupDim );
448 void XclImpPCField::ConvertDateGroupField( ScDPSaveData& rSaveData, const ScfStringVec& rVisNames ) const
450 ScDPNumGroupInfo aDateInfo( GetScDateGroupInfo() );
451 sal_Int32 nScDateType = maNumGroupInfo.GetScDateType();
453 switch( meFieldType )
455 case EXC_PCFIELD_DATEGROUP:
457 if( aDateInfo.mbDateValues )
459 // special case for days only with step value - create numeric grouping
460 ScDPSaveNumGroupDimension aNumGroupDim( GetFieldName( rVisNames ), aDateInfo );
461 rSaveData.GetDimensionData()->AddNumGroupDimension( aNumGroupDim );
463 else
465 ScDPSaveNumGroupDimension aNumGroupDim( GetFieldName( rVisNames ), ScDPNumGroupInfo() );
466 aNumGroupDim.SetDateInfo( aDateInfo, nScDateType );
467 rSaveData.GetDimensionData()->AddNumGroupDimension( aNumGroupDim );
470 break;
472 case EXC_PCFIELD_DATECHILD:
474 if( const XclImpPCField* pBaseField = GetGroupBaseField() )
476 const OUString& rBaseFieldName = pBaseField->GetFieldName( rVisNames );
477 if( !rBaseFieldName.isEmpty() )
479 ScDPSaveGroupDimension aGroupDim( rBaseFieldName, GetFieldName( rVisNames ) );
480 aGroupDim.SetDateInfo( aDateInfo, nScDateType );
481 rSaveData.GetDimensionData()->AddGroupDimension( aGroupDim );
485 break;
487 default:
488 OSL_FAIL( "XclImpPCField::ConvertDateGroupField - unknown date field type" );
492 ScDPNumGroupInfo XclImpPCField::GetScNumGroupInfo() const
494 ScDPNumGroupInfo aNumInfo;
495 aNumInfo.mbEnable = true;
496 aNumInfo.mbDateValues = false;
497 aNumInfo.mbAutoStart = true;
498 aNumInfo.mbAutoEnd = true;
500 if( const double* pfMinValue = GetNumGroupLimit( EXC_SXFIELD_INDEX_MIN ) )
502 aNumInfo.mfStart = *pfMinValue;
503 aNumInfo.mbAutoStart = ::get_flag( maNumGroupInfo.mnFlags, EXC_SXNUMGROUP_AUTOMIN );
505 if( const double* pfMaxValue = GetNumGroupLimit( EXC_SXFIELD_INDEX_MAX ) )
507 aNumInfo.mfEnd = *pfMaxValue;
508 aNumInfo.mbAutoEnd = ::get_flag( maNumGroupInfo.mnFlags, EXC_SXNUMGROUP_AUTOMAX );
510 if( const double* pfStepValue = GetNumGroupLimit( EXC_SXFIELD_INDEX_STEP ) )
511 aNumInfo.mfStep = *pfStepValue;
513 return aNumInfo;
516 ScDPNumGroupInfo XclImpPCField::GetScDateGroupInfo() const
518 ScDPNumGroupInfo aDateInfo;
519 aDateInfo.mbEnable = true;
520 aDateInfo.mbDateValues = false;
521 aDateInfo.mbAutoStart = true;
522 aDateInfo.mbAutoEnd = true;
524 if( const DateTime* pMinDate = GetDateGroupLimit( EXC_SXFIELD_INDEX_MIN ) )
526 aDateInfo.mfStart = GetDoubleFromDateTime( *pMinDate );
527 aDateInfo.mbAutoStart = ::get_flag( maNumGroupInfo.mnFlags, EXC_SXNUMGROUP_AUTOMIN );
529 if( const DateTime* pMaxDate = GetDateGroupLimit( EXC_SXFIELD_INDEX_MAX ) )
531 aDateInfo.mfEnd = GetDoubleFromDateTime( *pMaxDate );
532 aDateInfo.mbAutoEnd = ::get_flag( maNumGroupInfo.mnFlags, EXC_SXNUMGROUP_AUTOMAX );
534 // GetDateGroupStep() returns a value for date type "day" in single date groups only
535 if( const sal_Int16* pnStepValue = GetDateGroupStep() )
537 aDateInfo.mfStep = *pnStepValue;
538 aDateInfo.mbDateValues = true;
541 return aDateInfo;
544 const double* XclImpPCField::GetNumGroupLimit( sal_uInt16 nLimitIdx ) const
546 OSL_ENSURE( IsNumGroupField(), "XclImpPCField::GetNumGroupLimit - only for numeric grouping fields" );
547 if( const XclImpPCItem* pItem = GetLimitItem( nLimitIdx ) )
549 OSL_ENSURE( pItem->GetDouble(), "XclImpPCField::GetNumGroupLimit - SXDOUBLE item expected" );
550 return pItem->GetDouble();
552 return nullptr;
555 const DateTime* XclImpPCField::GetDateGroupLimit( sal_uInt16 nLimitIdx ) const
557 OSL_ENSURE( IsDateGroupField(), "XclImpPCField::GetDateGroupLimit - only for date grouping fields" );
558 if( const XclImpPCItem* pItem = GetLimitItem( nLimitIdx ) )
560 OSL_ENSURE( pItem->GetDateTime(), "XclImpPCField::GetDateGroupLimit - SXDATETIME item expected" );
561 return pItem->GetDateTime();
563 return nullptr;
566 const sal_Int16* XclImpPCField::GetDateGroupStep() const
568 // only for single date grouping fields, not for grouping chains
569 if( !IsGroupBaseField() && !IsGroupChildField() )
571 // only days may have a step value, return 0 for all other date types
572 if( maNumGroupInfo.GetXclDataType() == EXC_SXNUMGROUP_TYPE_DAY )
574 if( const XclImpPCItem* pItem = GetLimitItem( EXC_SXFIELD_INDEX_STEP ) )
576 OSL_ENSURE( pItem->GetInteger(), "XclImpPCField::GetDateGroupStep - SXINTEGER item expected" );
577 if( const sal_Int16* pnStep = pItem->GetInteger() )
579 OSL_ENSURE( *pnStep > 0, "XclImpPCField::GetDateGroupStep - invalid step count" );
580 // return nothing for step count 1 - this is also a standard date group in Excel
581 return (*pnStep > 1) ? pnStep : nullptr;
586 return nullptr;
589 XclImpPivotCache::XclImpPivotCache( const XclImpRoot& rRoot ) :
590 XclImpRoot( rRoot ),
591 maSrcRange( ScAddress::INITIALIZE_INVALID ),
592 mnStrmId( 0 ),
593 mnSrcType( EXC_SXVS_UNKNOWN ),
594 mbSelfRef( false )
598 XclImpPivotCache::~XclImpPivotCache()
602 // data access ----------------------------------------------------------------
604 const XclImpPCField* XclImpPivotCache::GetField( sal_uInt16 nFieldIdx ) const
606 return (nFieldIdx < maFields.size()) ? maFields[ nFieldIdx ].get() : nullptr;
609 // records --------------------------------------------------------------------
611 void XclImpPivotCache::ReadSxidstm( XclImpStream& rStrm )
613 mnStrmId = rStrm.ReaduInt16();
616 void XclImpPivotCache::ReadSxvs( XclImpStream& rStrm )
618 mnSrcType = rStrm.ReaduInt16();
619 GetTracer().TracePivotDataSource( mnSrcType != EXC_SXVS_SHEET );
622 void XclImpPivotCache::ReadDconref( XclImpStream& rStrm )
624 /* Read DCONREF only once (by checking maTabName), there may be other
625 DCONREF records in another context. Read reference only if a leading
626 SXVS record is present (by checking mnSrcType). */
627 if( !maTabName.isEmpty() || (mnSrcType != EXC_SXVS_SHEET) )
628 return;
630 XclRange aXclRange( ScAddress::UNINITIALIZED );
631 aXclRange.Read( rStrm, false );
632 OUString aEncUrl = rStrm.ReadUniString();
634 XclImpUrlHelper::DecodeUrl( maUrl, maTabName, mbSelfRef, GetRoot(), aEncUrl );
636 /* Do not convert maTabName to Calc sheet name -> original name is used to
637 find the sheet in the document. Sheet index of source range will be
638 found later in XclImpPivotCache::ReadPivotCacheStream(), because sheet
639 may not exist yet. */
640 if( mbSelfRef )
641 GetAddressConverter().ConvertRange( maSrcRange, aXclRange, 0, 0, true );
644 void XclImpPivotCache::ReadDConName( XclImpStream& rStrm )
646 maSrcRangeName = rStrm.ReadUniString();
648 // This 2-byte value equals the length of string that follows, or if 0 it
649 // indicates that the name has a workbook scope. For now, we only support
650 // internal defined name with a workbook scope.
651 sal_uInt16 nFlag;
652 nFlag = rStrm.ReaduInt16();
653 mbSelfRef = (nFlag == 0);
655 if (!mbSelfRef)
656 // External name is not supported yet.
657 maSrcRangeName.clear();
660 void XclImpPivotCache::ReadPivotCacheStream( const XclImpStream& rStrm )
662 if( (mnSrcType != EXC_SXVS_SHEET) && (mnSrcType != EXC_SXVS_EXTERN) )
663 return;
665 ScDocument& rDoc = GetDoc();
666 SCCOL nFieldScCol = 0; // column index of source data for next field
667 SCROW nItemScRow = 0; // row index of source data for current items
668 SCTAB nScTab = 0; // sheet index of source data
669 bool bGenerateSource = false; // true = write source data from cache to dummy table
671 if( mbSelfRef )
673 if (maSrcRangeName.isEmpty())
675 // try to find internal sheet containing the source data
676 nScTab = GetTabInfo().GetScTabFromXclName( maTabName );
677 if( rDoc.HasTable( nScTab ) )
679 // set sheet index to source range
680 maSrcRange.aStart.SetTab( nScTab );
681 maSrcRange.aEnd.SetTab( nScTab );
683 else
685 // create dummy sheet for deleted internal sheet
686 bGenerateSource = true;
690 else
692 // create dummy sheet for external sheet
693 bGenerateSource = true;
696 // create dummy sheet for source data from external or deleted sheet
697 if( bGenerateSource )
699 if( rDoc.GetTableCount() >= MAXTABCOUNT )
700 // cannot create more sheets -> exit
701 return;
703 nScTab = rDoc.GetTableCount();
704 rDoc.MakeTable( nScTab );
705 OUStringBuffer aDummyName("DPCache");
706 if( maTabName.getLength() > 0 )
707 aDummyName.append( "_" + maTabName );
708 OUString aName = aDummyName.makeStringAndClear();
709 rDoc.CreateValidTabName( aName );
710 rDoc.RenameTab( nScTab, aName );
711 // set sheet index to source range
712 maSrcRange.aStart.SetTab( nScTab );
713 maSrcRange.aEnd.SetTab( nScTab );
716 // open pivot cache storage stream
717 rtl::Reference<SotStorage> xSvStrg = OpenStorage(EXC_STORAGE_PTCACHE);
718 rtl::Reference<SotStorageStream> xSvStrm = OpenStream(xSvStrg, ScfTools::GetHexStr(mnStrmId));
719 if( !xSvStrm.is() )
720 return;
722 // create Excel record stream object
723 XclImpStream aPCStrm( *xSvStrm, GetRoot() );
724 aPCStrm.CopyDecrypterFrom( rStrm ); // pivot cache streams are encrypted
726 XclImpPCFieldRef xCurrField; // current field for new items
727 XclImpPCFieldVec aOrigFields; // all standard fields with inline original items
728 XclImpPCFieldVec aPostpFields; // all standard fields with postponed original items
729 size_t nPostpIdx = 0; // index to current field with postponed items
730 bool bLoop = true; // true = continue loop
732 while( bLoop && aPCStrm.StartNextRecord() )
734 switch( aPCStrm.GetRecId() )
736 case EXC_ID_EOF:
737 bLoop = false;
738 break;
740 case EXC_ID_SXDB:
741 aPCStrm >> maPCInfo;
742 break;
744 case EXC_ID_SXFIELD:
746 xCurrField.reset();
747 sal_uInt16 nNewFieldIdx = static_cast< sal_uInt16 >( maFields.size() );
748 if( nNewFieldIdx < EXC_PC_MAXFIELDCOUNT )
750 xCurrField = std::make_shared<XclImpPCField>( GetRoot(), *this, nNewFieldIdx );
751 maFields.push_back( xCurrField );
752 xCurrField->ReadSxfield( aPCStrm );
753 if( xCurrField->HasOrigItems() )
755 if( xCurrField->HasPostponedItems() )
756 aPostpFields.push_back( xCurrField );
757 else
758 aOrigFields.push_back( xCurrField );
759 // insert field name into generated source data, field remembers its column index
760 if( bGenerateSource && (nFieldScCol <= rDoc.MaxCol()) )
761 xCurrField->WriteFieldNameToSource( nFieldScCol++, nScTab );
763 // do not read items into invalid/postponed fields
764 if( !xCurrField->HasInlineItems() )
765 xCurrField.reset();
768 break;
770 case EXC_ID_SXINDEXLIST:
771 // read index list and insert all items into generated source data
772 if( bGenerateSource && (nItemScRow <= rDoc.MaxRow()) && (++nItemScRow <= rDoc.MaxRow()) )
774 for( const auto& rxOrigField : aOrigFields )
776 sal_uInt16 nItemIdx = rxOrigField->Has16BitIndexes() ? aPCStrm.ReaduInt16() : aPCStrm.ReaduInt8();
777 rxOrigField->WriteOrigItemToSource( nItemScRow, nScTab, nItemIdx );
780 xCurrField.reset();
781 break;
783 case EXC_ID_SXDOUBLE:
784 case EXC_ID_SXBOOLEAN:
785 case EXC_ID_SXERROR:
786 case EXC_ID_SXINTEGER:
787 case EXC_ID_SXSTRING:
788 case EXC_ID_SXDATETIME:
789 case EXC_ID_SXEMPTY:
790 if( xCurrField ) // inline items
792 xCurrField->ReadItem( aPCStrm );
794 else if( !aPostpFields.empty() ) // postponed items
796 // read postponed item
797 aPostpFields[ nPostpIdx ]->ReadItem( aPCStrm );
798 // write item to source
799 if( bGenerateSource && (nItemScRow <= rDoc.MaxRow()) )
801 // start new row, if there are only postponed fields
802 if( aOrigFields.empty() && (nPostpIdx == 0) )
803 ++nItemScRow;
804 if( nItemScRow <= rDoc.MaxRow() )
805 aPostpFields[ nPostpIdx ]->WriteLastOrigItemToSource( nItemScRow, nScTab );
807 // get index of next postponed field
808 ++nPostpIdx;
809 if( nPostpIdx >= aPostpFields.size() )
810 nPostpIdx = 0;
812 break;
814 case EXC_ID_SXNUMGROUP:
815 if( xCurrField )
816 xCurrField->ReadSxnumgroup( aPCStrm );
817 break;
819 case EXC_ID_SXGROUPINFO:
820 if( xCurrField )
821 xCurrField->ReadSxgroupinfo( aPCStrm );
822 break;
824 // known but ignored records
825 case EXC_ID_SXRULE:
826 case EXC_ID_SXFILT:
827 case EXC_ID_00F5:
828 case EXC_ID_SXNAME:
829 case EXC_ID_SXPAIR:
830 case EXC_ID_SXFMLA:
831 case EXC_ID_SXFORMULA:
832 case EXC_ID_SXDBEX:
833 case EXC_ID_SXFDBTYPE:
834 break;
836 default:
837 SAL_WARN("sc.filter", "XclImpPivotCache::ReadPivotCacheStream - unknown record 0x" << std::hex << aPCStrm.GetRecId() );
841 OSL_ENSURE( maPCInfo.mnTotalFields == maFields.size(),
842 "XclImpPivotCache::ReadPivotCacheStream - field count mismatch" );
844 if (static_cast<bool>(maPCInfo.mnFlags & EXC_SXDB_SAVEDATA))
846 SCROW nNewEnd = maSrcRange.aStart.Row() + maPCInfo.mnSrcRecs;
847 maSrcRange.aEnd.SetRow(nNewEnd);
850 // set source range for external source data
851 if( bGenerateSource && (nFieldScCol > 0) )
853 maSrcRange.aStart.SetCol( 0 );
854 maSrcRange.aStart.SetRow( 0 );
855 // nFieldScCol points to first unused column
856 maSrcRange.aEnd.SetCol( nFieldScCol - 1 );
857 // nItemScRow points to last used row
858 maSrcRange.aEnd.SetRow( nItemScRow );
862 bool XclImpPivotCache::IsRefreshOnLoad() const
864 return static_cast<bool>(maPCInfo.mnFlags & EXC_SXDB_REFRESH_LOAD);
867 bool XclImpPivotCache::IsValid() const
869 if (!maSrcRangeName.isEmpty())
870 return true;
872 return maSrcRange.IsValid();
875 // Pivot table
877 XclImpPTItem::XclImpPTItem( const XclImpPCField* pCacheField ) :
878 mpCacheField( pCacheField )
882 const OUString* XclImpPTItem::GetItemName() const
884 if( mpCacheField )
885 if( const XclImpPCItem* pCacheItem = mpCacheField->GetItem( maItemInfo.mnCacheIdx ) )
886 //TODO: use XclImpPCItem::ConvertToText(), if all conversions are available
887 return pCacheItem->IsEmpty() ? nullptr : pCacheItem->GetText();
888 return nullptr;
891 std::pair<bool, OUString> XclImpPTItem::GetItemName(const ScDPSaveDimension& rSaveDim, ScDPObject* pObj, const XclImpRoot& rRoot) const
893 if(!mpCacheField)
894 return std::pair<bool, OUString>(false, OUString());
896 const XclImpPCItem* pCacheItem = mpCacheField->GetItem( maItemInfo.mnCacheIdx );
897 if(!pCacheItem)
898 return std::pair<bool, OUString>(false, OUString());
900 OUString sItemName;
901 if(pCacheItem->GetType() == EXC_PCITEM_TEXT || pCacheItem->GetType() == EXC_PCITEM_ERROR)
903 const OUString* pItemName = pCacheItem->GetText();
904 if(!pItemName)
905 return std::pair<bool, OUString>(false, OUString());
906 sItemName = *pItemName;
908 else if (pCacheItem->GetType() == EXC_PCITEM_DOUBLE)
910 sItemName = pObj->GetFormattedString(rSaveDim.GetName(), *pCacheItem->GetDouble());
912 else if (pCacheItem->GetType() == EXC_PCITEM_INTEGER)
914 sItemName = pObj->GetFormattedString(rSaveDim.GetName(), static_cast<double>(*pCacheItem->GetInteger()));
916 else if (pCacheItem->GetType() == EXC_PCITEM_BOOL)
918 sItemName = pObj->GetFormattedString(rSaveDim.GetName(), static_cast<double>(*pCacheItem->GetBool()));
920 else if (pCacheItem->GetType() == EXC_PCITEM_DATETIME)
922 sItemName = pObj->GetFormattedString(rSaveDim.GetName(), rRoot.GetDoubleFromDateTime(*pCacheItem->GetDateTime()));
924 else if (pCacheItem->GetType() == EXC_PCITEM_EMPTY)
926 // sItemName is an empty string
928 else // EXC_PCITEM_INVALID
929 return std::pair<bool, OUString>(false, OUString());
931 return std::pair<bool, OUString>(true, sItemName);
934 void XclImpPTItem::ReadSxvi( XclImpStream& rStrm )
936 rStrm >> maItemInfo;
939 void XclImpPTItem::ConvertItem( ScDPSaveDimension& rSaveDim, ScDPObject* pObj, const XclImpRoot& rRoot ) const
941 // Find member and set properties
942 std::pair<bool, OUString> aReturnedName = GetItemName(rSaveDim, pObj, rRoot);
943 if(aReturnedName.first)
945 ScDPSaveMember* pMember = rSaveDim.GetExistingMemberByName(aReturnedName.second);
946 if(pMember)
948 pMember->SetIsVisible( !::get_flag( maItemInfo.mnFlags, EXC_SXVI_HIDDEN ) );
949 pMember->SetShowDetails( !::get_flag( maItemInfo.mnFlags, EXC_SXVI_HIDEDETAIL ) );
950 if (maItemInfo.HasVisName())
951 pMember->SetLayoutName(*maItemInfo.GetVisName());
956 XclImpPTField::XclImpPTField( const XclImpPivotTable& rPTable, sal_uInt16 nCacheIdx ) :
957 mrPTable( rPTable )
959 maFieldInfo.mnCacheIdx = nCacheIdx;
962 // general field/item access --------------------------------------------------
964 const XclImpPCField* XclImpPTField::GetCacheField() const
966 XclImpPivotCacheRef xPCache = mrPTable.GetPivotCache();
967 return xPCache ? xPCache->GetField( maFieldInfo.mnCacheIdx ) : nullptr;
970 OUString XclImpPTField::GetFieldName() const
972 const XclImpPCField* pField = GetCacheField();
973 return pField ? pField->GetFieldName( mrPTable.GetVisFieldNames() ) : OUString();
976 OUString XclImpPTField::GetVisFieldName() const
978 const OUString* pVisName = maFieldInfo.GetVisName();
979 return pVisName ? *pVisName : OUString();
982 const XclImpPTItem* XclImpPTField::GetItem( sal_uInt16 nItemIdx ) const
984 return (nItemIdx < maItems.size()) ? maItems[ nItemIdx ].get() : nullptr;
987 const OUString* XclImpPTField::GetItemName( sal_uInt16 nItemIdx ) const
989 const XclImpPTItem* pItem = GetItem( nItemIdx );
990 return pItem ? pItem->GetItemName() : nullptr;
993 // records --------------------------------------------------------------------
995 void XclImpPTField::ReadSxvd( XclImpStream& rStrm )
997 rStrm >> maFieldInfo;
1000 void XclImpPTField::ReadSxvdex( XclImpStream& rStrm )
1002 rStrm >> maFieldExtInfo;
1005 void XclImpPTField::ReadSxvi( XclImpStream& rStrm )
1007 XclImpPTItemRef xItem = std::make_shared<XclImpPTItem>( GetCacheField() );
1008 maItems.push_back( xItem );
1009 xItem->ReadSxvi( rStrm );
1012 // row/column fields ----------------------------------------------------------
1014 void XclImpPTField::ConvertRowColField( ScDPSaveData& rSaveData ) const
1016 OSL_ENSURE( maFieldInfo.mnAxes & EXC_SXVD_AXIS_ROWCOL, "XclImpPTField::ConvertRowColField - no row/column field" );
1017 // special data orientation field?
1018 if( maFieldInfo.mnCacheIdx == EXC_SXIVD_DATA )
1019 rSaveData.GetDataLayoutDimension()->SetOrientation( maFieldInfo.GetApiOrient( EXC_SXVD_AXIS_ROWCOL ) );
1020 else
1021 ConvertRCPField( rSaveData );
1024 // page fields ----------------------------------------------------------------
1026 void XclImpPTField::SetPageFieldInfo( const XclPTPageFieldInfo& rPageInfo )
1028 maPageInfo = rPageInfo;
1031 void XclImpPTField::ConvertPageField( ScDPSaveData& rSaveData ) const
1033 OSL_ENSURE( maFieldInfo.mnAxes & EXC_SXVD_AXIS_PAGE, "XclImpPTField::ConvertPageField - no page field" );
1034 ConvertRCPField( rSaveData );
1037 // hidden fields --------------------------------------------------------------
1039 void XclImpPTField::ConvertHiddenField( ScDPSaveData& rSaveData ) const
1041 OSL_ENSURE( (maFieldInfo.mnAxes & EXC_SXVD_AXIS_ROWCOLPAGE) == 0, "XclImpPTField::ConvertHiddenField - field not hidden" );
1042 ConvertRCPField( rSaveData );
1045 // data fields ----------------------------------------------------------------
1047 bool XclImpPTField::HasDataFieldInfo() const
1049 return !maDataInfoVector.empty();
1052 void XclImpPTField::AddDataFieldInfo( const XclPTDataFieldInfo& rDataInfo )
1054 OSL_ENSURE( maFieldInfo.mnAxes & EXC_SXVD_AXIS_DATA, "XclImpPTField::AddDataFieldInfo - no data field" );
1055 maDataInfoVector.push_back( rDataInfo );
1058 void XclImpPTField::ConvertDataField( ScDPSaveData& rSaveData ) const
1060 OSL_ENSURE( maFieldInfo.mnAxes & EXC_SXVD_AXIS_DATA, "XclImpPTField::ConvertDataField - no data field" );
1061 OSL_ENSURE( !maDataInfoVector.empty(), "XclImpPTField::ConvertDataField - no data field info" );
1062 if (maDataInfoVector.empty())
1063 return;
1065 OUString aFieldName = GetFieldName();
1066 if (aFieldName.isEmpty())
1067 return;
1069 ScDPSaveDimension* pSaveDim = rSaveData.GetNewDimensionByName(aFieldName);
1070 if (!pSaveDim)
1072 SAL_WARN("sc.filter","XclImpPTField::ConvertDataField - field name not found: " << aFieldName);
1073 return;
1076 auto aIt = maDataInfoVector.begin(), aEnd = maDataInfoVector.end();
1078 ConvertDataField( *pSaveDim, *aIt );
1080 // multiple data fields -> clone dimension
1081 for( ++aIt; aIt != aEnd; ++aIt )
1083 ScDPSaveDimension& rDupDim = rSaveData.DuplicateDimension( *pSaveDim );
1084 ConvertDataFieldInfo( rDupDim, *aIt );
1088 // private --------------------------------------------------------------------
1091 * Convert Excel-encoded subtotal name to a Calc-encoded one.
1093 static OUString lcl_convertExcelSubtotalName(const OUString& rName)
1095 OUStringBuffer aBuf;
1096 const sal_Unicode* p = rName.getStr();
1097 sal_Int32 n = rName.getLength();
1098 for (sal_Int32 i = 0; i < n; ++i)
1100 const sal_Unicode c = p[i];
1101 if (c == '\\')
1103 aBuf.append(OUStringChar(c) + OUStringChar(c));
1105 else
1106 aBuf.append(c);
1108 return aBuf.makeStringAndClear();
1111 void XclImpPTField::ConvertRCPField( ScDPSaveData& rSaveData ) const
1113 const OUString aFieldName = GetFieldName();
1114 if( aFieldName.isEmpty() )
1115 return;
1117 const XclImpPCField* pCacheField = GetCacheField();
1118 if( !pCacheField || !pCacheField->IsSupportedField() )
1119 return;
1121 ScDPSaveDimension* pTest = rSaveData.GetNewDimensionByName(aFieldName);
1122 if (!pTest)
1123 return;
1125 ScDPSaveDimension& rSaveDim = *pTest;
1127 // orientation
1128 rSaveDim.SetOrientation( maFieldInfo.GetApiOrient( EXC_SXVD_AXIS_ROWCOLPAGE ) );
1130 // visible name
1131 if (const OUString* pVisName = maFieldInfo.GetVisName())
1132 if (!pVisName->isEmpty())
1133 rSaveDim.SetLayoutName( *pVisName );
1135 // subtotal function(s)
1136 XclPTSubtotalVec aSubtotalVec;
1137 maFieldInfo.GetSubtotals( aSubtotalVec );
1138 if( !aSubtotalVec.empty() )
1139 rSaveDim.SetSubTotals( std::move(aSubtotalVec) );
1141 // sorting
1142 DataPilotFieldSortInfo aSortInfo;
1143 aSortInfo.Field = mrPTable.GetDataFieldName( maFieldExtInfo.mnSortField );
1144 aSortInfo.IsAscending = ::get_flag( maFieldExtInfo.mnFlags, EXC_SXVDEX_SORT_ASC );
1145 aSortInfo.Mode = maFieldExtInfo.GetApiSortMode();
1146 rSaveDim.SetSortInfo( &aSortInfo );
1148 // auto show
1149 DataPilotFieldAutoShowInfo aShowInfo;
1150 aShowInfo.IsEnabled = ::get_flag( maFieldExtInfo.mnFlags, EXC_SXVDEX_AUTOSHOW );
1151 aShowInfo.ShowItemsMode = maFieldExtInfo.GetApiAutoShowMode();
1152 aShowInfo.ItemCount = maFieldExtInfo.GetApiAutoShowCount();
1153 aShowInfo.DataField = mrPTable.GetDataFieldName( maFieldExtInfo.mnShowField );
1154 rSaveDim.SetAutoShowInfo( &aShowInfo );
1156 // layout
1157 DataPilotFieldLayoutInfo aLayoutInfo;
1158 aLayoutInfo.LayoutMode = maFieldExtInfo.GetApiLayoutMode();
1159 aLayoutInfo.AddEmptyLines = ::get_flag( maFieldExtInfo.mnFlags, EXC_SXVDEX_LAYOUT_BLANK );
1160 rSaveDim.SetLayoutInfo( &aLayoutInfo );
1162 // grouping info
1163 pCacheField->ConvertGroupField( rSaveData, mrPTable.GetVisFieldNames() );
1165 // custom subtotal name
1166 if (maFieldExtInfo.mpFieldTotalName)
1168 OUString aSubName = lcl_convertExcelSubtotalName(*maFieldExtInfo.mpFieldTotalName);
1169 rSaveDim.SetSubtotalName(aSubName);
1173 void XclImpPTField::ConvertFieldInfo( const ScDPSaveData& rSaveData, ScDPObject* pObj, const XclImpRoot& rRoot, bool bPageField ) const
1175 const OUString aFieldName = GetFieldName();
1176 if( aFieldName.isEmpty() )
1177 return;
1179 const XclImpPCField* pCacheField = GetCacheField();
1180 if( !pCacheField || !pCacheField->IsSupportedField() )
1181 return;
1183 ScDPSaveDimension* pSaveDim = rSaveData.GetExistingDimensionByName(aFieldName);
1184 if (!pSaveDim)
1185 return;
1187 pSaveDim->SetShowEmpty( ::get_flag( maFieldExtInfo.mnFlags, EXC_SXVDEX_SHOWALL ) );
1188 for( const auto& rxItem : maItems )
1189 rxItem->ConvertItem( *pSaveDim, pObj, rRoot );
1191 if(bPageField && maPageInfo.mnSelItem != EXC_SXPI_ALLITEMS)
1193 const XclImpPTItem* pItem = GetItem( maPageInfo.mnSelItem );
1194 if(pItem)
1196 std::pair<bool, OUString> aReturnedName = pItem->GetItemName(*pSaveDim, pObj, rRoot);
1197 if(aReturnedName.first)
1198 pSaveDim->SetCurrentPage(&aReturnedName.second);
1203 void XclImpPTField::ConvertDataField( ScDPSaveDimension& rSaveDim, const XclPTDataFieldInfo& rDataInfo ) const
1205 // orientation
1206 rSaveDim.SetOrientation( DataPilotFieldOrientation_DATA );
1207 // extended data field info
1208 ConvertDataFieldInfo( rSaveDim, rDataInfo );
1211 void XclImpPTField::ConvertDataFieldInfo( ScDPSaveDimension& rSaveDim, const XclPTDataFieldInfo& rDataInfo ) const
1213 // visible name
1214 const OUString* pVisName = rDataInfo.GetVisName();
1215 if (pVisName && !pVisName->isEmpty())
1216 rSaveDim.SetLayoutName(*pVisName);
1218 // aggregation function
1219 rSaveDim.SetFunction( rDataInfo.GetApiAggFunc() );
1221 // result field reference
1222 sal_Int32 nRefType = rDataInfo.GetApiRefType();
1223 DataPilotFieldReference aFieldRef;
1224 aFieldRef.ReferenceType = nRefType;
1225 const XclImpPTField* pRefField = mrPTable.GetField(rDataInfo.mnRefField);
1226 if (pRefField)
1228 aFieldRef.ReferenceField = pRefField->GetFieldName();
1229 aFieldRef.ReferenceItemType = rDataInfo.GetApiRefItemType();
1230 if (aFieldRef.ReferenceItemType == sheet::DataPilotFieldReferenceItemType::NAMED)
1232 const OUString* pRefItemName = pRefField->GetItemName(rDataInfo.mnRefItem);
1233 if (pRefItemName)
1234 aFieldRef.ReferenceItemName = *pRefItemName;
1238 rSaveDim.SetReferenceValue(&aFieldRef);
1241 XclImpPivotTable::XclImpPivotTable( const XclImpRoot& rRoot ) :
1242 XclImpRoot( rRoot ),
1243 maDataOrientField( *this, EXC_SXIVD_DATA ),
1244 mpDPObj(nullptr)
1248 XclImpPivotTable::~XclImpPivotTable()
1252 // cache/field access, misc. --------------------------------------------------
1254 sal_uInt16 XclImpPivotTable::GetFieldCount() const
1256 return static_cast< sal_uInt16 >( maFields.size() );
1259 const XclImpPTField* XclImpPivotTable::GetField( sal_uInt16 nFieldIdx ) const
1261 return (nFieldIdx == EXC_SXIVD_DATA) ? &maDataOrientField :
1262 ((nFieldIdx < maFields.size()) ? maFields[ nFieldIdx ].get() : nullptr);
1265 XclImpPTField* XclImpPivotTable::GetFieldAcc( sal_uInt16 nFieldIdx )
1267 // do not return maDataOrientField
1268 return (nFieldIdx < maFields.size()) ? maFields[ nFieldIdx ].get() : nullptr;
1271 const XclImpPTField* XclImpPivotTable::GetDataField( sal_uInt16 nDataFieldIdx ) const
1273 if( nDataFieldIdx < maOrigDataFields.size() )
1274 return GetField( maOrigDataFields[ nDataFieldIdx ] );
1275 return nullptr;
1278 OUString XclImpPivotTable::GetDataFieldName( sal_uInt16 nDataFieldIdx ) const
1280 if( const XclImpPTField* pField = GetDataField( nDataFieldIdx ) )
1281 return pField->GetFieldName();
1282 return OUString();
1285 // records --------------------------------------------------------------------
1287 void XclImpPivotTable::ReadSxview( XclImpStream& rStrm )
1289 rStrm >> maPTInfo;
1291 GetAddressConverter().ConvertRange(
1292 maOutScRange, maPTInfo.maOutXclRange, GetCurrScTab(), GetCurrScTab(), true );
1294 mxPCache = GetPivotTableManager().GetPivotCache( maPTInfo.mnCacheIdx );
1295 mxCurrField.reset();
1298 void XclImpPivotTable::ReadSxvd( XclImpStream& rStrm )
1300 sal_uInt16 nFieldCount = GetFieldCount();
1301 if( nFieldCount < EXC_PT_MAXFIELDCOUNT )
1303 // cache index for the field is equal to the SXVD record index
1304 mxCurrField = std::make_shared<XclImpPTField>( *this, nFieldCount );
1305 maFields.push_back( mxCurrField );
1306 mxCurrField->ReadSxvd( rStrm );
1307 // add visible name of new field to list of visible names
1308 maVisFieldNames.push_back( mxCurrField->GetVisFieldName() );
1309 OSL_ENSURE( maFields.size() == maVisFieldNames.size(),
1310 "XclImpPivotTable::ReadSxvd - wrong size of visible name array" );
1312 else
1313 mxCurrField.reset();
1316 void XclImpPivotTable::ReadSxvi( XclImpStream& rStrm )
1318 if( mxCurrField )
1319 mxCurrField->ReadSxvi( rStrm );
1322 void XclImpPivotTable::ReadSxvdex( XclImpStream& rStrm )
1324 if( mxCurrField )
1325 mxCurrField->ReadSxvdex( rStrm );
1328 void XclImpPivotTable::ReadSxivd( XclImpStream& rStrm )
1330 mxCurrField.reset();
1332 // find the index vector to fill (row SXIVD doesn't exist without row fields)
1333 ScfUInt16Vec* pFieldVec = nullptr;
1334 if( maRowFields.empty() && (maPTInfo.mnRowFields > 0) )
1335 pFieldVec = &maRowFields;
1336 else if( maColFields.empty() && (maPTInfo.mnColFields > 0) )
1337 pFieldVec = &maColFields;
1339 // fill the vector from record data
1340 if( !pFieldVec )
1341 return;
1343 sal_uInt16 nSize = ulimit_cast< sal_uInt16 >( rStrm.GetRecSize() / 2, EXC_PT_MAXROWCOLCOUNT );
1344 pFieldVec->reserve( nSize );
1345 for( sal_uInt16 nIdx = 0; nIdx < nSize; ++nIdx )
1347 sal_uInt16 nFieldIdx;
1348 nFieldIdx = rStrm.ReaduInt16();
1349 pFieldVec->push_back( nFieldIdx );
1351 // set orientation at special data orientation field
1352 if( nFieldIdx == EXC_SXIVD_DATA )
1354 sal_uInt16 nAxis = (pFieldVec == &maRowFields) ? EXC_SXVD_AXIS_ROW : EXC_SXVD_AXIS_COL;
1355 maDataOrientField.SetAxes( nAxis );
1360 void XclImpPivotTable::ReadSxpi( XclImpStream& rStrm )
1362 mxCurrField.reset();
1364 sal_uInt16 nSize = ulimit_cast< sal_uInt16 >( rStrm.GetRecSize() / 6 );
1365 for( sal_uInt16 nEntry = 0; nEntry < nSize; ++nEntry )
1367 XclPTPageFieldInfo aPageInfo;
1368 rStrm >> aPageInfo;
1369 if( XclImpPTField* pField = GetFieldAcc( aPageInfo.mnField ) )
1371 maPageFields.push_back( aPageInfo.mnField );
1372 pField->SetPageFieldInfo( aPageInfo );
1374 GetCurrSheetDrawing().SetSkipObj( aPageInfo.mnObjId );
1378 void XclImpPivotTable::ReadSxdi( XclImpStream& rStrm )
1380 mxCurrField.reset();
1382 XclPTDataFieldInfo aDataInfo;
1383 rStrm >> aDataInfo;
1384 if( XclImpPTField* pField = GetFieldAcc( aDataInfo.mnField ) )
1386 maOrigDataFields.push_back( aDataInfo.mnField );
1387 // DataPilot does not support double data fields -> add first appearance to index list only
1388 if( !pField->HasDataFieldInfo() )
1389 maFiltDataFields.push_back( aDataInfo.mnField );
1390 pField->AddDataFieldInfo( aDataInfo );
1394 void XclImpPivotTable::ReadSxex( XclImpStream& rStrm )
1396 rStrm >> maPTExtInfo;
1399 void XclImpPivotTable::ReadSxViewEx9( XclImpStream& rStrm )
1401 rStrm >> maPTViewEx9Info;
1404 void XclImpPivotTable::ReadSxAddl( XclImpStream& rStrm )
1406 rStrm >> maPTAddlInfo;
1409 void XclImpPivotTable::Convert()
1411 if( !mxPCache || !mxPCache->IsValid() )
1412 return;
1414 if (comphelper::IsFuzzing()) //just too slow
1415 return;
1417 ScDPSaveData aSaveData;
1419 // *** global settings ***
1421 aSaveData.SetRowGrand( ::get_flag( maPTInfo.mnFlags, EXC_SXVIEW_ROWGRAND ) );
1422 aSaveData.SetColumnGrand( ::get_flag( maPTInfo.mnFlags, EXC_SXVIEW_COLGRAND ) );
1423 aSaveData.SetFilterButton( false );
1424 aSaveData.SetDrillDown( ::get_flag( maPTExtInfo.mnFlags, EXC_SXEX_DRILLDOWN ) );
1425 aSaveData.SetIgnoreEmptyRows( false );
1426 aSaveData.SetRepeatIfEmpty( false );
1428 // *** fields ***
1430 // row fields
1431 for( const auto& rRowField : maRowFields )
1432 if( const XclImpPTField* pField = GetField( rRowField ) )
1433 pField->ConvertRowColField( aSaveData );
1435 // column fields
1436 for( const auto& rColField : maColFields )
1437 if( const XclImpPTField* pField = GetField( rColField ) )
1438 pField->ConvertRowColField( aSaveData );
1440 // page fields
1441 for( const auto& rPageField : maPageFields )
1442 if( const XclImpPTField* pField = GetField( rPageField ) )
1443 pField->ConvertPageField( aSaveData );
1445 // We need to import hidden fields because hidden fields may contain
1446 // special settings for subtotals (aggregation function, filters, custom
1447 // name etc.) and members (hidden, custom name etc.).
1449 // hidden fields
1450 for( sal_uInt16 nField = 0, nCount = GetFieldCount(); nField < nCount; ++nField )
1451 if( const XclImpPTField* pField = GetField( nField ) )
1452 if (!pField->GetAxes())
1453 pField->ConvertHiddenField( aSaveData );
1455 // data fields
1456 for( const auto& rFiltDataField : maFiltDataFields )
1457 if( const XclImpPTField* pField = GetField( rFiltDataField ) )
1458 pField->ConvertDataField( aSaveData );
1460 // *** insert into Calc document ***
1462 // create source descriptor
1463 ScSheetSourceDesc aDesc(&GetDoc());
1464 const OUString& rSrcName = mxPCache->GetSourceRangeName();
1465 if (!rSrcName.isEmpty())
1466 // Range name is the data source.
1467 aDesc.SetRangeName(rSrcName);
1468 else
1469 // Normal cell range.
1470 aDesc.SetSourceRange(mxPCache->GetSourceRange());
1472 // adjust output range to include the page fields
1473 ScRange aOutRange( maOutScRange );
1474 if( !maPageFields.empty() )
1476 SCROW nDecRows = ::std::min< SCROW >( aOutRange.aStart.Row(), maPageFields.size() + 1 );
1477 aOutRange.aStart.IncRow( -nDecRows );
1480 // create the DataPilot
1481 std::unique_ptr<ScDPObject> pDPObj(new ScDPObject( &GetDoc() ));
1482 pDPObj->SetName( maPTInfo.maTableName );
1483 if (!maPTInfo.maDataName.isEmpty())
1484 aSaveData.GetDataLayoutDimension()->SetLayoutName(maPTInfo.maDataName);
1486 if (!maPTViewEx9Info.maGrandTotalName.isEmpty())
1487 aSaveData.SetGrandTotalName(maPTViewEx9Info.maGrandTotalName);
1489 pDPObj->SetSaveData( aSaveData );
1490 pDPObj->SetSheetDesc( aDesc );
1491 pDPObj->SetOutRange( aOutRange );
1492 pDPObj->SetHeaderLayout( maPTViewEx9Info.mnGridLayout == 0 );
1494 mpDPObj = GetDoc().GetDPCollection()->InsertNewTable(std::move(pDPObj));
1496 ApplyFieldInfo();
1497 ApplyMergeFlags(aOutRange, aSaveData);
1500 void XclImpPivotTable::MaybeRefresh()
1502 if (mpDPObj && mxPCache->IsRefreshOnLoad())
1504 // 'refresh table on load' flag is set. Refresh the table now. Some
1505 // Excel files contain partial table output when this flag is set.
1506 ScRange aOutRange = mpDPObj->GetOutRange();
1507 mpDPObj->Output(aOutRange.aStart);
1511 void XclImpPivotTable::ApplyMergeFlags(const ScRange& rOutRange, const ScDPSaveData& rSaveData)
1513 // Apply merge flags for various datapilot controls.
1515 ScDPOutputGeometry aGeometry(rOutRange, false);
1516 aGeometry.setColumnFieldCount(maPTInfo.mnColFields);
1517 aGeometry.setPageFieldCount(maPTInfo.mnPageFields);
1518 aGeometry.setDataFieldCount(maPTInfo.mnDataFields);
1519 aGeometry.setRowFieldCount(maPTInfo.mnRowFields);
1521 // Make sure we set headerlayout when input file has additional raw header
1522 if(maPTInfo.mnColFields == 0)
1524 mpDPObj->SetHeaderLayout( maPTInfo.mnFirstHeadRow - 2 == static_cast<sal_uInt16>(aGeometry.getRowFieldHeaderRow()) );
1526 aGeometry.setHeaderLayout(mpDPObj->GetHeaderLayout());
1527 aGeometry.setCompactMode(maPTAddlInfo.mbCompactMode);
1529 ScDocument& rDoc = GetDoc();
1531 vector<const ScDPSaveDimension*> aFieldDims;
1532 vector<ScAddress> aFieldBtns;
1534 aGeometry.getPageFieldPositions(aFieldBtns);
1535 for (const auto& rFieldBtn : aFieldBtns)
1537 rDoc.ApplyFlagsTab(rFieldBtn.Col(), rFieldBtn.Row(), rFieldBtn.Col(), rFieldBtn.Row(), rFieldBtn.Tab(), ScMF::Button);
1539 ScMF nMFlag = ScMF::ButtonPopup;
1540 OUString aName = rDoc.GetString(rFieldBtn.Col(), rFieldBtn.Row(), rFieldBtn.Tab());
1541 if (rSaveData.HasInvisibleMember(aName))
1542 nMFlag |= ScMF::HiddenMember;
1544 rDoc.ApplyFlagsTab(rFieldBtn.Col()+1, rFieldBtn.Row(), rFieldBtn.Col()+1, rFieldBtn.Row(), rFieldBtn.Tab(), nMFlag);
1547 aGeometry.getColumnFieldPositions(aFieldBtns);
1548 rSaveData.GetAllDimensionsByOrientation(sheet::DataPilotFieldOrientation_COLUMN, aFieldDims);
1549 if (aFieldBtns.size() == aFieldDims.size())
1551 vector<const ScDPSaveDimension*>::const_iterator itDim = aFieldDims.begin();
1552 for (const auto& rFieldBtn : aFieldBtns)
1554 ScMF nMFlag = ScMF::Button;
1555 const ScDPSaveDimension* pDim = *itDim;
1556 if (pDim->HasInvisibleMember())
1557 nMFlag |= ScMF::HiddenMember;
1558 if (!pDim->IsDataLayout())
1559 nMFlag |= ScMF::ButtonPopup;
1560 rDoc.ApplyFlagsTab(rFieldBtn.Col(), rFieldBtn.Row(), rFieldBtn.Col(), rFieldBtn.Row(), rFieldBtn.Tab(), nMFlag);
1561 ++itDim;
1565 aGeometry.getRowFieldPositions(aFieldBtns);
1566 rSaveData.GetAllDimensionsByOrientation(sheet::DataPilotFieldOrientation_ROW, aFieldDims);
1567 if (!((aFieldBtns.size() == aFieldDims.size()) || (maPTAddlInfo.mbCompactMode && aFieldBtns.size() == 1)))
1568 return;
1570 vector<const ScDPSaveDimension*>::const_iterator itDim = aFieldDims.begin();
1571 for (const auto& rFieldBtn : aFieldBtns)
1573 ScMF nMFlag = ScMF::Button;
1574 const ScDPSaveDimension* pDim = itDim != aFieldDims.end() ? *itDim++ : nullptr;
1575 if (pDim && pDim->HasInvisibleMember())
1576 nMFlag |= ScMF::HiddenMember;
1577 if (!pDim || !pDim->IsDataLayout())
1578 nMFlag |= ScMF::ButtonPopup;
1579 rDoc.ApplyFlagsTab(rFieldBtn.Col(), rFieldBtn.Row(), rFieldBtn.Col(), rFieldBtn.Row(), rFieldBtn.Tab(), nMFlag);
1584 void XclImpPivotTable::ApplyFieldInfo()
1586 mpDPObj->BuildAllDimensionMembers();
1587 ScDPSaveData& rSaveData = *mpDPObj->GetSaveData();
1589 // row fields
1590 for( const auto& rRowField : maRowFields )
1591 if( const XclImpPTField* pField = GetField( rRowField ) )
1592 pField->ConvertFieldInfo( rSaveData, mpDPObj, *this );
1594 // column fields
1595 for( const auto& rColField : maColFields )
1596 if( const XclImpPTField* pField = GetField( rColField ) )
1597 pField->ConvertFieldInfo( rSaveData, mpDPObj, *this );
1599 // page fields
1600 for( const auto& rPageField : maPageFields )
1601 if( const XclImpPTField* pField = GetField( rPageField ) )
1602 pField->ConvertFieldInfo( rSaveData, mpDPObj, *this, true );
1604 // hidden fields
1605 for( sal_uInt16 nField = 0, nCount = GetFieldCount(); nField < nCount; ++nField )
1606 if( const XclImpPTField* pField = GetField( nField ) )
1607 if (!pField->GetAxes())
1608 pField->ConvertFieldInfo( rSaveData, mpDPObj, *this );
1611 XclImpPivotTableManager::XclImpPivotTableManager( const XclImpRoot& rRoot ) :
1612 XclImpRoot( rRoot )
1616 XclImpPivotTableManager::~XclImpPivotTableManager()
1620 // pivot cache records --------------------------------------------------------
1622 XclImpPivotCacheRef XclImpPivotTableManager::GetPivotCache( sal_uInt16 nCacheIdx )
1624 XclImpPivotCacheRef xPCache;
1625 if( nCacheIdx < maPCaches.size() )
1626 xPCache = maPCaches[ nCacheIdx ];
1627 return xPCache;
1630 void XclImpPivotTableManager::ReadSxidstm( XclImpStream& rStrm )
1632 XclImpPivotCacheRef xPCache = std::make_shared<XclImpPivotCache>( GetRoot() );
1633 maPCaches.push_back( xPCache );
1634 xPCache->ReadSxidstm( rStrm );
1637 void XclImpPivotTableManager::ReadSxvs( XclImpStream& rStrm )
1639 if( !maPCaches.empty() )
1640 maPCaches.back()->ReadSxvs( rStrm );
1643 void XclImpPivotTableManager::ReadDconref( XclImpStream& rStrm )
1645 if( !maPCaches.empty() )
1646 maPCaches.back()->ReadDconref( rStrm );
1649 void XclImpPivotTableManager::ReadDConName( XclImpStream& rStrm )
1651 if( !maPCaches.empty() )
1652 maPCaches.back()->ReadDConName( rStrm );
1655 // pivot table records --------------------------------------------------------
1657 void XclImpPivotTableManager::ReadSxview( XclImpStream& rStrm )
1659 XclImpPivotTableRef xPTable = std::make_shared<XclImpPivotTable>( GetRoot() );
1660 maPTables.push_back( xPTable );
1661 xPTable->ReadSxview( rStrm );
1664 void XclImpPivotTableManager::ReadSxvd( XclImpStream& rStrm )
1666 if( !maPTables.empty() )
1667 maPTables.back()->ReadSxvd( rStrm );
1670 void XclImpPivotTableManager::ReadSxvdex( XclImpStream& rStrm )
1672 if( !maPTables.empty() )
1673 maPTables.back()->ReadSxvdex( rStrm );
1676 void XclImpPivotTableManager::ReadSxivd( XclImpStream& rStrm )
1678 if( !maPTables.empty() )
1679 maPTables.back()->ReadSxivd( rStrm );
1682 void XclImpPivotTableManager::ReadSxpi( XclImpStream& rStrm )
1684 if( !maPTables.empty() )
1685 maPTables.back()->ReadSxpi( rStrm );
1688 void XclImpPivotTableManager::ReadSxdi( XclImpStream& rStrm )
1690 if( !maPTables.empty() )
1691 maPTables.back()->ReadSxdi( rStrm );
1694 void XclImpPivotTableManager::ReadSxvi( XclImpStream& rStrm )
1696 if( !maPTables.empty() )
1697 maPTables.back()->ReadSxvi( rStrm );
1700 void XclImpPivotTableManager::ReadSxex( XclImpStream& rStrm )
1702 if( !maPTables.empty() )
1703 maPTables.back()->ReadSxex( rStrm );
1706 void XclImpPivotTableManager::ReadSxViewEx9( XclImpStream& rStrm )
1708 if( !maPTables.empty() )
1709 maPTables.back()->ReadSxViewEx9( rStrm );
1712 void XclImpPivotTableManager::ReadSxAddl( XclImpStream& rStrm )
1714 if( !maPTables.empty() )
1715 maPTables.back()->ReadSxAddl( rStrm );
1718 void XclImpPivotTableManager::ReadPivotCaches( const XclImpStream& rStrm )
1720 for( auto& rxPCache : maPCaches )
1721 rxPCache->ReadPivotCacheStream( rStrm );
1724 void XclImpPivotTableManager::ConvertPivotTables()
1726 for( auto& rxPTable : maPTables )
1727 rxPTable->Convert();
1730 void XclImpPivotTableManager::MaybeRefreshPivotTables()
1732 for( auto& rxPTable : maPTables )
1733 rxPTable->MaybeRefresh();
1736 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */