xref: /trunk/main/sc/source/core/data/dpdimsave.cxx (revision b3f79822)
1 /**************************************************************
2  *
3  * Licensed to the Apache Software Foundation (ASF) under one
4  * or more contributor license agreements.  See the NOTICE file
5  * distributed with this work for additional information
6  * regarding copyright ownership.  The ASF licenses this file
7  * to you under the Apache License, Version 2.0 (the
8  * "License"); you may not use this file except in compliance
9  * with the License.  You may obtain a copy of the License at
10  *
11  *   http://www.apache.org/licenses/LICENSE-2.0
12  *
13  * Unless required by applicable law or agreed to in writing,
14  * software distributed under the License is distributed on an
15  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16  * KIND, either express or implied.  See the License for the
17  * specific language governing permissions and limitations
18  * under the License.
19  *
20  *************************************************************/
21 
22 
23 
24 // MARKER(update_precomp.py): autogen include statement, do not remove
25 #include "precompiled_sc.hxx"
26 
27 #include "dpdimsave.hxx"
28 #include "dpgroup.hxx"
29 #include "dpobject.hxx"
30 #include "document.hxx"
31 
32 #include <com/sun/star/sheet/DataPilotFieldGroupBy.hpp>
33 
34 #include <svl/zforlist.hxx>
35 #include <tools/debug.hxx>
36 #include <rtl/math.hxx>
37 #include <algorithm>
38 
39 // ============================================================================
40 
41 ScDPSaveGroupItem::ScDPSaveGroupItem( const String& rName ) :
42     aGroupName( rName )
43 {
44 }
45 
46 ScDPSaveGroupItem::~ScDPSaveGroupItem()
47 {
48 }
49 
50 void ScDPSaveGroupItem::AddElement( const String& rName )
51 {
52     aElements.push_back( rName );
53 }
54 
55 void ScDPSaveGroupItem::AddElementsFromGroup( const ScDPSaveGroupItem& rGroup )
56 {
57     // add all elements of the other group (used for nested grouping)
58 
59     for ( std::vector<String>::const_iterator aIter(rGroup.aElements.begin());
60                                 aIter != rGroup.aElements.end(); aIter++ )
61         aElements.push_back( *aIter );
62 }
63 
64 bool ScDPSaveGroupItem::RemoveElement( const String& rName )
65 {
66     for ( std::vector<String>::iterator aIter(aElements.begin()); aIter != aElements.end(); aIter++ )
67         if ( *aIter == rName )          //! ignore case
68         {
69             aElements.erase( aIter );   // found -> remove
70             return true;                // don't have to look further
71         }
72 
73     return false;   // not found
74 }
75 
76 bool ScDPSaveGroupItem::IsEmpty() const
77 {
78     return aElements.empty();
79 }
80 
81 size_t ScDPSaveGroupItem::GetElementCount() const
82 {
83     return aElements.size();
84 }
85 
86 const String* ScDPSaveGroupItem::GetElementByIndex( size_t nIndex ) const
87 {
88     return (nIndex < aElements.size()) ? &aElements[ nIndex ] : 0;
89 }
90 
91 void ScDPSaveGroupItem::Rename( const String& rNewName )
92 {
93     aGroupName = rNewName;
94 }
95 
96 void ScDPSaveGroupItem::RemoveElementsFromGroups( ScDPSaveGroupDimension& rDimension ) const
97 {
98     // remove this group's elements from their groups in rDimension
99     // (rDimension must be a different dimension from the one which contains this)
100 
101     for ( std::vector<String>::const_iterator aIter(aElements.begin()); aIter != aElements.end(); aIter++ )
102         rDimension.RemoveFromGroups( *aIter );
103 }
104 
105 void ScDPSaveGroupItem::AddToData( ScDPGroupDimension& rDataDim, SvNumberFormatter* pFormatter ) const
106 {
107     ScDPGroupItem aGroup( aGroupName );
108     ScDPItemData aData;
109 
110     for ( std::vector<String>::const_iterator aIter(aElements.begin()); aIter != aElements.end(); aIter++ )
111     {
112         sal_uInt32 nFormat = 0;      //! ...
113         double fValue;
114         if ( pFormatter->IsNumberFormat( *aIter, nFormat, fValue ) )
115             aData = ScDPItemData( *aIter, fValue, sal_True );
116         else
117             aData.SetString( *aIter );
118 
119         aGroup.AddElement( aData );
120         //! for numeric data, look at source members?
121     }
122 
123     rDataDim.AddItem( aGroup );
124 }
125 
126 // ============================================================================
127 
128 ScDPSaveGroupDimension::ScDPSaveGroupDimension( const String& rSource, const String& rName ) :
129     aSourceDim( rSource ),
130     aGroupDimName( rName ),
131     nDatePart( 0 )
132 {
133 }
134 
135 ScDPSaveGroupDimension::ScDPSaveGroupDimension( const String& rSource, const String& rName, const ScDPNumGroupInfo& rDateInfo, sal_Int32 nPart ) :
136     aSourceDim( rSource ),
137     aGroupDimName( rName ),
138     aDateInfo( rDateInfo ),
139     nDatePart( nPart )
140 {
141 }
142 
143 ScDPSaveGroupDimension::~ScDPSaveGroupDimension()
144 {
145 }
146 
147 void ScDPSaveGroupDimension::SetDateInfo( const ScDPNumGroupInfo& rInfo, sal_Int32 nPart )
148 {
149     aDateInfo = rInfo;
150     nDatePart = nPart;
151 }
152 
153 void ScDPSaveGroupDimension::AddGroupItem( const ScDPSaveGroupItem& rItem )
154 {
155     aGroups.push_back( rItem );
156 }
157 
158 String ScDPSaveGroupDimension::CreateGroupName( const String& rPrefix )
159 {
160     // create a name for a new group, using "Group1", "Group2" etc. (translated prefix in rPrefix)
161 
162     //! look in all dimensions, to avoid clashes with automatic groups (=name of base element)?
163     //! (only dimensions for the same base)
164 
165     sal_Int32 nAdd = 1;                                 // first try is "Group1"
166     const sal_Int32 nMaxAdd = nAdd + aGroups.size();    // limit the loop
167     while ( nAdd <= nMaxAdd )
168     {
169         String aGroupName( rPrefix );
170         aGroupName.Append( String::CreateFromInt32( nAdd ) );
171         bool bExists = false;
172 
173         // look for existing groups
174         for ( ScDPSaveGroupItemVec::const_iterator aIter(aGroups.begin());
175                                     aIter != aGroups.end() && !bExists; aIter++ )
176             if ( aIter->GetGroupName() == aGroupName )         //! ignore case
177                 bExists = true;
178 
179         if ( !bExists )
180             return aGroupName;          // found a new name
181 
182         ++nAdd;                         // continue with higher number
183     }
184 
185     DBG_ERROR("CreateGroupName: no valid name found");
186     return EMPTY_STRING;
187 }
188 
189 const ScDPSaveGroupItem* ScDPSaveGroupDimension::GetNamedGroup( const String& rGroupName ) const
190 {
191     return const_cast< ScDPSaveGroupDimension* >( this )->GetNamedGroupAcc( rGroupName );
192 }
193 
194 ScDPSaveGroupItem* ScDPSaveGroupDimension::GetNamedGroupAcc( const String& rGroupName )
195 {
196     for ( ScDPSaveGroupItemVec::iterator aIter(aGroups.begin()); aIter != aGroups.end(); aIter++ )
197         if ( aIter->GetGroupName() == rGroupName )         //! ignore case
198             return &*aIter;
199 
200     return NULL;        // none found
201 }
202 
203 long ScDPSaveGroupDimension::GetGroupCount() const
204 {
205     return aGroups.size();
206 }
207 
208 const ScDPSaveGroupItem* ScDPSaveGroupDimension::GetGroupByIndex( long nIndex ) const
209 {
210     return const_cast< ScDPSaveGroupDimension* >( this )->GetGroupAccByIndex( nIndex );
211 }
212 
213 ScDPSaveGroupItem* ScDPSaveGroupDimension::GetGroupAccByIndex( long nIndex )
214 {
215     return &aGroups[nIndex];
216 }
217 
218 void ScDPSaveGroupDimension::RemoveFromGroups( const String& rItemName )
219 {
220     //  if the item is in any group, remove it from the group,
221     //  also remove the group if it is empty afterwards
222 
223     for ( ScDPSaveGroupItemVec::iterator aIter(aGroups.begin()); aIter != aGroups.end(); aIter++ )
224         if ( aIter->RemoveElement( rItemName ) )
225         {
226             if ( aIter->IsEmpty() )         // removed last item from the group?
227                 aGroups.erase( aIter );     // then remove the group
228 
229             return;     // don't have to look further
230         }
231 }
232 
233 void ScDPSaveGroupDimension::RemoveGroup( const String& rGroupName )
234 {
235     for ( ScDPSaveGroupItemVec::iterator aIter(aGroups.begin()); aIter != aGroups.end(); aIter++ )
236         if ( aIter->GetGroupName() == rGroupName )          //! ignore case
237         {
238             aGroups.erase( aIter );
239             return;                     // don't have to look further
240         }
241 }
242 
243 bool ScDPSaveGroupDimension::IsEmpty() const
244 {
245     return aGroups.empty();
246 }
247 
248 bool ScDPSaveGroupDimension::HasOnlyHidden( const ScStrCollection& rVisible )
249 {
250     // check if there are only groups that don't appear in the list of visible names
251 
252     bool bAllHidden = true;
253     for ( ScDPSaveGroupItemVec::const_iterator aIter(aGroups.begin()); aIter != aGroups.end() && bAllHidden; aIter++ )
254     {
255         StrData aSearch( aIter->GetGroupName() );
256         sal_uInt16 nCollIndex;
257         if ( rVisible.Search( &aSearch, nCollIndex ) )
258             bAllHidden = false;                             // found one that is visible
259     }
260     return bAllHidden;
261 }
262 
263 void ScDPSaveGroupDimension::Rename( const String& rNewName )
264 {
265     aGroupDimName = rNewName;
266 }
267 
268 void ScDPSaveGroupDimension::AddToData( ScDPGroupTableData& rData ) const
269 {
270     long nSourceIndex = rData.GetDimensionIndex( aSourceDim );
271     if ( nSourceIndex >= 0 )
272     {
273         ScDPGroupDimension aDim( nSourceIndex, aGroupDimName );
274         if ( nDatePart )
275         {
276             // date grouping
277 
278             aDim.MakeDateHelper( aDateInfo, nDatePart );
279         }
280         else
281         {
282             // normal (manual) grouping
283 
284             SvNumberFormatter* pFormatter = rData.GetDocument()->GetFormatTable();
285 
286             for ( ScDPSaveGroupItemVec::const_iterator aIter(aGroups.begin()); aIter != aGroups.end(); aIter++ )
287                 aIter->AddToData( aDim, pFormatter );
288         }
289 
290         rData.AddGroupDimension( aDim );
291     }
292 }
293 
294 // ============================================================================
295 
296 ScDPSaveNumGroupDimension::ScDPSaveNumGroupDimension( const String& rName, const ScDPNumGroupInfo& rInfo ) :
297     aDimensionName( rName ),
298     aGroupInfo( rInfo ),
299     nDatePart( 0 )
300 {
301 }
302 
303 ScDPSaveNumGroupDimension::ScDPSaveNumGroupDimension( const String& rName, const ScDPNumGroupInfo& rDateInfo, sal_Int32 nPart ) :
304     aDimensionName( rName ),
305     aDateInfo( rDateInfo ),
306     nDatePart( nPart )
307 {
308 }
309 
310 ScDPSaveNumGroupDimension::~ScDPSaveNumGroupDimension()
311 {
312 }
313 
314 void ScDPSaveNumGroupDimension::AddToData( ScDPGroupTableData& rData ) const
315 {
316     long nSource = rData.GetDimensionIndex( aDimensionName );
317     if ( nSource >= 0 )
318     {
319         ScDPNumGroupDimension aDim( aGroupInfo );           // aGroupInfo: value grouping
320         if ( nDatePart )
321             aDim.MakeDateHelper( aDateInfo, nDatePart );    // date grouping
322 
323         rData.SetNumGroupDimension( nSource, aDim );
324     }
325 }
326 
327 void ScDPSaveNumGroupDimension::SetGroupInfo( const ScDPNumGroupInfo& rNew )
328 {
329     aGroupInfo = rNew;
330 }
331 
332 void ScDPSaveNumGroupDimension::SetDateInfo( const ScDPNumGroupInfo& rInfo, sal_Int32 nPart )
333 {
334     aDateInfo = rInfo;
335     nDatePart = nPart;
336 }
337 
338 // ============================================================================
339 
340 namespace {
341 
342 struct ScDPSaveGroupDimNameFunc
343 {
344     String              maDimName;
345     inline explicit     ScDPSaveGroupDimNameFunc( const String& rDimName ) : maDimName( rDimName ) {}
346     inline bool         operator()( const ScDPSaveGroupDimension& rGroupDim ) const { return rGroupDim.GetGroupDimName() == maDimName; }
347 };
348 
349 struct ScDPSaveGroupSourceNameFunc
350 {
351     String              maSrcDimName;
352     inline explicit     ScDPSaveGroupSourceNameFunc( const String& rSrcDimName ) : maSrcDimName( rSrcDimName ) {}
353     inline bool         operator()( const ScDPSaveGroupDimension& rGroupDim ) const { return rGroupDim.GetSourceDimName() == maSrcDimName; }
354 };
355 
356 } // namespace
357 
358 // ----------------------------------------------------------------------------
359 
360 ScDPDimensionSaveData::ScDPDimensionSaveData()
361 {
362 }
363 
364 ScDPDimensionSaveData::~ScDPDimensionSaveData()
365 {
366 }
367 
368 bool ScDPDimensionSaveData::operator==( const ScDPDimensionSaveData& ) const
369 {
370     return false;
371 }
372 
373 void ScDPDimensionSaveData::AddGroupDimension( const ScDPSaveGroupDimension& rGroupDim )
374 {
375     DBG_ASSERT( ::std::find_if( maGroupDims.begin(), maGroupDims.end(), ScDPSaveGroupDimNameFunc( rGroupDim.GetGroupDimName() ) ) == maGroupDims.end(),
376         "ScDPDimensionSaveData::AddGroupDimension - group dimension exists already" );
377     // ReplaceGroupDimension() adds new or replaces existing
378     ReplaceGroupDimension( rGroupDim );
379 }
380 
381 void ScDPDimensionSaveData::ReplaceGroupDimension( const ScDPSaveGroupDimension& rGroupDim )
382 {
383     ScDPSaveGroupDimVec::iterator aIt = ::std::find_if(
384         maGroupDims.begin(), maGroupDims.end(), ScDPSaveGroupDimNameFunc( rGroupDim.GetGroupDimName() ) );
385     if( aIt == maGroupDims.end() )
386         maGroupDims.push_back( rGroupDim );
387     else
388         *aIt = rGroupDim;
389 }
390 
391 void ScDPDimensionSaveData::RemoveGroupDimension( const String& rGroupDimName )
392 {
393     ScDPSaveGroupDimVec::iterator aIt = ::std::find_if(
394         maGroupDims.begin(), maGroupDims.end(), ScDPSaveGroupDimNameFunc( rGroupDimName ) );
395     if( aIt != maGroupDims.end() )
396         maGroupDims.erase( aIt );
397 }
398 
399 void ScDPDimensionSaveData::AddNumGroupDimension( const ScDPSaveNumGroupDimension& rGroupDim )
400 {
401     DBG_ASSERT( maNumGroupDims.count( rGroupDim.GetDimensionName() ) == 0,
402         "ScDPDimensionSaveData::AddNumGroupDimension - numeric group dimension exists already" );
403     // ReplaceNumGroupDimension() adds new or replaces existing
404     ReplaceNumGroupDimension( rGroupDim );
405 }
406 
407 void ScDPDimensionSaveData::ReplaceNumGroupDimension( const ScDPSaveNumGroupDimension& rGroupDim )
408 {
409     ScDPSaveNumGroupDimMap::iterator aIt = maNumGroupDims.find( rGroupDim.GetDimensionName() );
410     if( aIt == maNumGroupDims.end() )
411         maNumGroupDims.insert( ScDPSaveNumGroupDimMap::value_type( rGroupDim.GetDimensionName(), rGroupDim ) );
412     else
413         aIt->second = rGroupDim;
414 }
415 
416 void ScDPDimensionSaveData::RemoveNumGroupDimension( const String& rGroupDimName )
417 {
418     maNumGroupDims.erase( rGroupDimName );
419 }
420 
421 void ScDPDimensionSaveData::WriteToData( ScDPGroupTableData& rData ) const
422 {
423     //  rData is assumed to be empty
424     //  AddToData also handles date grouping
425 
426     for( ScDPSaveGroupDimVec::const_iterator aIt = maGroupDims.begin(), aEnd = maGroupDims.end(); aIt != aEnd; ++aIt )
427         aIt->AddToData( rData );
428 
429     for( ScDPSaveNumGroupDimMap::const_iterator aIt = maNumGroupDims.begin(), aEnd = maNumGroupDims.end(); aIt != aEnd; ++aIt )
430         aIt->second.AddToData( rData );
431 }
432 
433 const ScDPSaveGroupDimension* ScDPDimensionSaveData::GetGroupDimForBase( const String& rBaseDimName ) const
434 {
435     return const_cast< ScDPDimensionSaveData* >( this )->GetGroupDimAccForBase( rBaseDimName );
436 }
437 
438 const ScDPSaveGroupDimension* ScDPDimensionSaveData::GetNamedGroupDim( const String& rGroupDimName ) const
439 {
440     return const_cast< ScDPDimensionSaveData* >( this )->GetNamedGroupDimAcc( rGroupDimName );
441 }
442 
443 const ScDPSaveGroupDimension* ScDPDimensionSaveData::GetFirstNamedGroupDim( const String& rBaseDimName ) const
444 {
445     return const_cast< ScDPDimensionSaveData* >( this )->GetFirstNamedGroupDimAcc( rBaseDimName );
446 }
447 
448 const ScDPSaveGroupDimension* ScDPDimensionSaveData::GetNextNamedGroupDim( const String& rGroupDimName ) const
449 {
450     return const_cast< ScDPDimensionSaveData* >( this )->GetNextNamedGroupDimAcc( rGroupDimName );
451 }
452 
453 const ScDPSaveNumGroupDimension* ScDPDimensionSaveData::GetNumGroupDim( const String& rGroupDimName ) const
454 {
455     return const_cast< ScDPDimensionSaveData* >( this )->GetNumGroupDimAcc( rGroupDimName );
456 }
457 
458 ScDPSaveGroupDimension* ScDPDimensionSaveData::GetGroupDimAccForBase( const String& rBaseDimName )
459 {
460     ScDPSaveGroupDimension* pGroupDim = GetFirstNamedGroupDimAcc( rBaseDimName );
461     return pGroupDim ? pGroupDim : GetNextNamedGroupDimAcc( rBaseDimName );
462 }
463 
464 ScDPSaveGroupDimension* ScDPDimensionSaveData::GetNamedGroupDimAcc( const String& rGroupDimName )
465 {
466     ScDPSaveGroupDimVec::iterator aIt = ::std::find_if(
467         maGroupDims.begin(), maGroupDims.end(), ScDPSaveGroupDimNameFunc( rGroupDimName ) );
468     return (aIt == maGroupDims.end()) ? 0 : &*aIt;
469 }
470 
471 ScDPSaveGroupDimension* ScDPDimensionSaveData::GetFirstNamedGroupDimAcc( const String& rBaseDimName )
472 {
473     ScDPSaveGroupDimVec::iterator aIt = ::std::find_if(
474         maGroupDims.begin(), maGroupDims.end(), ScDPSaveGroupSourceNameFunc( rBaseDimName ) );
475     return (aIt == maGroupDims.end()) ? 0 : &*aIt;
476 }
477 
478 ScDPSaveGroupDimension* ScDPDimensionSaveData::GetNextNamedGroupDimAcc( const String& rGroupDimName )
479 {
480     // find the group dimension with the passed name
481     ScDPSaveGroupDimVec::iterator aIt = ::std::find_if(
482         maGroupDims.begin(), maGroupDims.end(), ScDPSaveGroupDimNameFunc( rGroupDimName ) );
483     // find next group dimension based on the same source dimension name
484     if( aIt != maGroupDims.end() )
485         aIt = ::std::find_if( aIt + 1, maGroupDims.end(), ScDPSaveGroupSourceNameFunc( aIt->GetSourceDimName() ) );
486     return (aIt == maGroupDims.end()) ? 0 : &*aIt;
487 }
488 
489 ScDPSaveNumGroupDimension* ScDPDimensionSaveData::GetNumGroupDimAcc( const String& rGroupDimName )
490 {
491     ScDPSaveNumGroupDimMap::iterator aIt = maNumGroupDims.find( rGroupDimName );
492     return (aIt == maNumGroupDims.end()) ? 0 : &aIt->second;
493 }
494 
495 bool ScDPDimensionSaveData::HasGroupDimensions() const
496 {
497     return !maGroupDims.empty() || !maNumGroupDims.empty();
498 }
499 
500 sal_Int32 ScDPDimensionSaveData::CollectDateParts( const String& rBaseDimName ) const
501 {
502     sal_Int32 nParts = 0;
503     // start with part of numeric group
504     if( const ScDPSaveNumGroupDimension* pNumDim = GetNumGroupDim( rBaseDimName ) )
505         nParts |= pNumDim->GetDatePart();
506     // collect parts from all matching group dimensions
507     for( const ScDPSaveGroupDimension* pGroupDim = GetFirstNamedGroupDim( rBaseDimName ); pGroupDim; pGroupDim = GetNextNamedGroupDim( pGroupDim->GetGroupDimName() ) )
508         nParts |= pGroupDim->GetDatePart();
509 
510     return nParts;
511 }
512 
513 String ScDPDimensionSaveData::CreateGroupDimName( const String& rSourceName,
514                                         const ScDPObject& rObject, bool bAllowSource,
515                                         const std::vector<String>* pDeletedNames )
516 {
517     // create a name for the new dimension by appending a number to the original
518     // dimension's name
519 
520     bool bUseSource = bAllowSource;     // if set, try the unchanged original name first
521 
522     sal_Int32 nAdd = 2;                 // first try is "Name2"
523     const sal_Int32 nMaxAdd = 1000;     // limit the loop
524     while ( nAdd <= nMaxAdd )
525     {
526         String aDimName( rSourceName );
527         if ( !bUseSource )
528             aDimName.Append( String::CreateFromInt32( nAdd ) );
529         bool bExists = false;
530 
531         // look for existing group dimensions
532         for( ScDPSaveGroupDimVec::const_iterator aIt = maGroupDims.begin(), aEnd = maGroupDims.end(); (aIt != aEnd) && !bExists; ++aIt )
533             if( aIt->GetGroupDimName() == aDimName )         //! ignore case
534                 bExists = true;
535 
536         // look for base dimensions that happen to have that name
537         if ( !bExists && rObject.IsDimNameInUse( aDimName ) )
538         {
539             if ( pDeletedNames &&
540                  std::find( pDeletedNames->begin(), pDeletedNames->end(), aDimName ) != pDeletedNames->end() )
541             {
542                 // allow the name anyway if the name is in pDeletedNames
543             }
544             else
545                 bExists = true;
546         }
547 
548         if ( !bExists )
549             return aDimName;            // found a new name
550 
551         if ( bUseSource )
552             bUseSource = false;
553         else
554             ++nAdd;                     // continue with higher number
555     }
556     DBG_ERROR("CreateGroupDimName: no valid name found");
557     return EMPTY_STRING;
558 }
559 
560 String ScDPDimensionSaveData::CreateDateGroupDimName( sal_Int32 nDatePart, const ScDPObject& rObject, bool bAllowSource, const ::std::vector< String >* pDeletedNames )
561 {
562     using namespace ::com::sun::star::sheet::DataPilotFieldGroupBy;
563     String aPartName;
564     switch( nDatePart )
565     {
566         //! use translated strings from globstr.src
567         case SECONDS:  aPartName = String::CreateFromAscii( "Seconds" );    break;
568         case MINUTES:  aPartName = String::CreateFromAscii( "Minutes" );    break;
569         case HOURS:    aPartName = String::CreateFromAscii( "Hours" );      break;
570         case DAYS:     aPartName = String::CreateFromAscii( "Days" );       break;
571         case MONTHS:   aPartName = String::CreateFromAscii( "Months" );     break;
572         case QUARTERS: aPartName = String::CreateFromAscii( "Quarters" );   break;
573         case YEARS:    aPartName = String::CreateFromAscii( "Years" );      break;
574     }
575     DBG_ASSERT( aPartName.Len() > 0, "ScDPDimensionSaveData::CreateDateGroupDimName - invalid date part" );
576     return CreateGroupDimName( aPartName, rObject, bAllowSource, pDeletedNames );
577 }
578 
579 // ============================================================================
580 
581