]> SALOME platform Git repositories - modules/gui.git/blob - src/DDS/DDS_Dictionary.cxx
Salome HOME
28c309574dc5435aabef61e3e453f00ea96be51c
[modules/gui.git] / src / DDS / DDS_Dictionary.cxx
1 // Copyright (C) 2007-2012  CEA/DEN, EDF R&D, OPEN CASCADE
2 //
3 // Copyright (C) 2003-2007  OPEN CASCADE, EADS/CCR, LIP6, CEA/DEN,
4 // CEDRAT, EDF R&D, LEG, PRINCIPIA R&D, BUREAU VERITAS
5 //
6 // This library is free software; you can redistribute it and/or
7 // modify it under the terms of the GNU Lesser General Public
8 // License as published by the Free Software Foundation; either
9 // version 2.1 of the License.
10 //
11 // This library is distributed in the hope that it will be useful,
12 // but WITHOUT ANY WARRANTY; without even the implied warranty of
13 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14 // Lesser General Public License for more details.
15 //
16 // You should have received a copy of the GNU Lesser General Public
17 // License along with this library; if not, write to the Free Software
18 // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
19 //
20 // See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com
21 //
22
23 #include "DDS_Dictionary.h"
24
25 #include "DDS_KeyWords.h"
26
27 #include <Basics_OCCTVersion.hxx>
28
29 #include <LDOMString.hxx>
30 #include <LDOMParser.hxx>
31
32 #include <UnitsAPI.hxx>
33
34 #include <TColStd_SequenceOfInteger.hxx>
35 #include <TColStd_SequenceOfAsciiString.hxx>
36 #include <TColStd_SequenceOfExtendedString.hxx>
37
38 #include <NCollection_Map.hxx>
39
40 #include <Standard_Failure.hxx>
41 #include <Standard_ErrorHandler.hxx>
42
43 IMPLEMENT_STANDARD_HANDLE(DDS_Dictionary, MMgt_TShared)
44 IMPLEMENT_STANDARD_RTTIEXT(DDS_Dictionary, MMgt_TShared)
45
46 /*!
47   \class DDS_Dictionary
48   \brief This class provides an information about used datums, 
49          reading them from XML file.
50
51   There is the only instance of the class DDS_Dictionary in the application
52   which can be retrieved by method Get().
53
54   Datum is a set of parameters describing a phisical characteristic.
55   These parameters are loaded from the XML file which has the following format:
56
57   \verbatim
58   <D_URI>
59   <COMPONENT COMPONENT_NAME="component_name">
60     <UNIT_SYSTEMS>
61       <UNIT_SYSTEM UNIT_SYSTEM_NAME="[system_internal_name]" UNIT_SYSTEM_LABEL="[system_label]">
62       <UNIT_SYSTEM UNIT_SYSTEM_NAME="[system_internal_name]" UNIT_SYSTEM_LABEL="[system_label]">
63       ...
64       <UNIT_SYSTEM UNIT_SYSTEM_NAME="[system_internal_name]" UNIT_SYSTEM_LABEL="[system_label]">
65     </UNIT_SYSTEMS>
66
67     <DATUM DATUM_UNITS="[base_system_internal_name]" DATUM_FORMAT="[sprintf_format_specification]"
68            DATUM_ID="[datum_id]" DATUM_LABEL="[datum_label]" DATUM_REQUIRED="[requred_value]"
69            DATUM_FILTER="[string_regular_expression]">
70       <DY_DOMAIN>
71         <VALUE_DESCR VD_DEFV="[default_value]" VD_MAXV="[max_value]" VD_MINV="[min_value]" VD_TYPE="[value_type]"/>
72         <VALUE_LIST_REF VLR_LIST="[referenced_list_id]"/>
73       </DY_DOMAIN>
74       <DESCR>
75         <SHORT_D>[brief_desription_text]</SHORT_D>
76         <LONG_D>[full_description_text]</LONG_D>
77       </DESCR>
78       <OPTIONS>
79         <OPTION OPTION_NAME="[option_name_1]">[option_value_1]</OPTION>
80         <OPTION OPTION_NAME="[option_name_2]">[option_value_2]</OPTION>
81         ...
82         <OPTION OPTION_NAME="[option_name_n]">[option_value_n]</OPTION>
83       </OPTIONS>
84     </DATUM>
85
86     ...
87
88     <VALUE_LIST VALUE_LIST_ID="[list_id]" VALUE_LIST_NAME="[list_name]">
89       <VALUE_LIST_VALUE VALUE_LIST_VALUEID="[list_element_id]" VALUE_LIST_VALUEICON="[list_element_icon_file]">[list_element_name]</VALUE_LIST_VALUE>
90       <VALUE_LIST_VALUE VALUE_LIST_VALUEID="[list_element_id]" VALUE_LIST_VALUEICON="[list_element_icon_file]">[list_element_name]</VALUE_LIST_VALUE>
91       ...
92       <VALUE_LIST_VALUE VALUE_LIST_VALUEID="[list_element_id]" VALUE_LIST_VALUEICON="[list_element_icon_file]">[list_element_name]</VALUE_LIST_VALUE>
93     </VALUE_LIST>
94
95     ...
96
97     </COMPONENT>
98   </D_URI>
99
100   \endverbatim
101
102   In above description of the datum XML file format internal keys are used as XML tags
103   and attributes names. Real XML keywords are defined by DDS_KeyWords class.
104
105   XML file should have one main tag named "dictionary" (key "D_URI"). This tag
106   should contain one or several components. 
107   Component is an independent set of datums and units systems.
108   Components are defined by XML tag named "component" (key "COMPONENT") with 
109   attribute "name" (key COMPONENT_NAME).
110   Component name is used as component identifier and should be unique. 
111   
112   Component tag can contain:
113
114   - Tag "unit_systems" (key UNIT_SYSTEMS) defines a set of used units systems.
115   At least one unit system named SI ("System International") should exist.
116   If this system does not exist, it will be created automatically.
117   Each units system is defined by XML tag "unit system" (key UNIT_SYSTEM) under 
118   the tag "unit_systems" with attributes "name" (key UNIT_SYSTEM_NAME) 
119   and "label" (key UNIT_SYSTEM_LABEL). Name is an identifier of the units system and
120   label is its human readable description.
121
122   - One or several tags "datum" (key DATUM). For this tag the following attributes 
123   can be defined:
124     -# Identifier (key DATUM_ID) specifies the unique id string for the datum.
125     -# Label (key DATUM_LABEL) specifies human readable name of the datum.
126     -# Measure units (key DATUM_UNITS) for the given units system. Attribute name 
127     defines a name of units system and a keyword got from DDS_KeyWords by key DATUM_UNITS.
128     For example, for "SI" units system and default keyword the attribute name is "SIunits".
129     This attribute should be specified for each declared units system. 
130     Value of this attribute should be a string describing measure units.
131     For possible designations for measure units and their multiple prefixes
132     please refer to the UnitsAPI package of the OpenCascade library
133     (files Units.dat and Lexi_Expr.dat). Measure units are used for numerical
134     values conversion from one units system to another one.
135     -# Format (key DATUM_FORMAT) specifies the format string which will be used
136     during initial formatting of the value. This string should be specified 
137     in sprintf() format.
138     -# Filter (key DATUM_FILTER) specifies the regualr expression. The value (string)
139     entered by the user will be checked up to match to this regular expression
140     (if it defined). Non matched strings will be rejected.
141     -# Required value (key DATUM_REQUIRED). If this attributed si defined and its value
142     is \c true then user can't leave an input non-filled - parameter must be explicitly
143     entered by the user).
144
145   - One or several tags "list definition" (key VALUE_LIST). Each such tag defines
146   the list of items for enumerable data. Attribute "list id" (key VALUE_LIST_ID) 
147   specifies the identifier string for the list and attribute "list name"
148   (key VALUE_LIST_NAME) defines a list name string. Each list item is described
149   by tag "list value" (key VALUE_LIST_VALUE) under the tag "list definition". 
150   Each this tag contains item string text and have the following attributes:
151     -# "list item id" (key VALUE_LIST_VALUEID) - integer numerical identifier for 
152     the item
153     -# "list item icon" (key VALUE_LIST_VALUEICON) - icon file name for the item
154
155   Tag "datum" can have child subtags "description" and "options".
156
157   - Tag "description" (key DESCR) contains two sub tags:
158     -# "short description" (key SHORT_D) specifies a brief datum description text
159     -# "long description" (key LONG_D) specifies a detailed description text
160
161   - Tag "options" (key OPTIONS) contains one or more sub tags "option" (key OPTION).
162   Each of these XML elements should contain text option value and attribute
163   "name" (key OPTION_NAME) which specifies option name.
164
165   Each tag "datum" defines most common parameters of phisical characteristic.
166   These parameters are placed in two groups:
167   -# Domain parameters under the tag "domain" (key DY_DOMAIN). This tag can 
168   contain value description tag (key VALUE_DESCR) for descrete data which is
169   described by following parameters:
170     - default value (key VD_DEFV)
171     - maximum value (key VD_MAXV)
172     - minimum value (key VD_MINV)
173     - type of value (key VD_TYPE), possible values are String, Integer, Float, List
174   -# list reference tag (key VALUE_LIST_REF) for enumerable data described by
175   "list reference" attribute (key VLR_LIST) which references to the list
176   (see "list definition" tag) by list id.
177
178   Below is an example of the XML file using default keywords.
179
180   \verbatim
181   <datadictionary version="1.0">
182     <component name="My Component">
183
184       <!-- specify two unit systems -->
185
186       <unitSystems>
187         <system name="SI" label="System international">
188         <system name="AS" label="Anglo - sacson system">
189       </unitSystems>
190
191       <!-- specify datum -->
192       <!-- units of measure for SI - meters (m), for AS - inches (in) -->
193       <datum SIunits="m" ASunits="in" format="%.25f" id="X" label="X coordinate" required="">
194         <domain>
195           <!-- default value not specified -->
196           <valueDescr default="" max="999999999.999" min="0.000" type="Float"/>
197         </domain>
198         <description>
199           <shortDescr>X coordinate for object</shortDescr>
200           <longDescr>X multiplier of object coordinates. Describe position of object in direction of X axis</longDescr>
201         </description>
202       </datum>
203
204       <datum SIunits="m" ASunits="in" format="%.25f" id="Y" label="Y coordinate" required="">
205         <domain>
206           <valueDescr default="" max="999999999.999" min="0.000" type="Float"/>
207         </domain>
208         <description>
209           <shortDescr>Y coordinate for object</shortDescr>
210           <longDescr>Y multiplier of object coordinates. Describe position of object in direction of Y axis</longDescr>
211         </description>
212       </datum>
213
214       <!-- datum for object name with filter which not allow to input more that 8 letters,
215            numbers or unerscores with first letter only -->
216       <datum format="%.8us" id="ObjectName" label="Name" required="yes"
217              filter="^([A-Z]+)([A-Z,0-9,_]*)$">
218         <domain>
219           <!-- limits and default not specified, type is string -->
220           <valueDescr default="" max="" min="" type="String" />
221         </domain>
222         <description>
223           <!-- long description not specified -->
224           <shortDescr>Name of object</shortDescr>
225         </description>
226       </datum>
227
228
229       <!-- datum for enumeration of side -->
230       <datum format="" id="Side" label="Side" required="">
231         <domain>
232           <!-- default list item is item with id 0 -->
233           <valueDescr default="0" type="List"/>
234           <!-- list reference on list named "side_list" -->
235           <listRef list="side_list"/>
236         </domain>
237         <description>
238           <shortDescr>Side of object</shortDescr>
239         </description>
240       </datum>
241
242       <!-- list definition for enumeration of side -->
243       <valueList listid="side_list" name="Side">
244         <value id="1">left</value>
245         <value id="2">right</value>
246         <value id="3">top</value>
247         <value id="4">bottom</value>
248         <value id="0">undefined</value>
249       </valueList>
250
251     </component>
252   </datadictionary>
253   \endverbatim
254 */
255
256 /*!
257   \brief Constructor.
258
259   Create an instance of the dictionary. Can not be used directly.
260   Use Get() method instead.
261 */
262 DDS_Dictionary::DDS_Dictionary()
263 : MMgt_TShared()
264 {
265 }
266
267 /*!
268   \brief Copy constructor (put in private section to prevent object copying).
269 */
270 DDS_Dictionary::DDS_Dictionary( const DDS_Dictionary& )
271 {
272 }
273
274 /*!
275   \brief Assignment operator (put in private section to prevent object copying).
276 */
277 void DDS_Dictionary::operator=( const DDS_Dictionary& )
278 {
279 }
280
281 /*!
282   \brief Get the names of defined units systems from all components.
283   \param theSystems returning sequence of units systems names.
284 */
285 void DDS_Dictionary::GetUnitSystems( TColStd_SequenceOfAsciiString& theSystems ) const
286 {
287   theSystems.Clear();
288
289   NCollection_Map<TCollection_AsciiString> aMap;
290   for ( Standard_Integer i = 1; i <= myGroupMap.Extent(); i++ )
291   {
292     TColStd_SequenceOfAsciiString theSeq;
293     myGroupMap.FindFromIndex( i )->GetUnitSystems( theSeq );
294     for ( Standard_Integer s = 1; s <= theSeq.Length(); s++ )
295     {
296       if ( aMap.Contains( theSeq.Value( s ) ) )
297         continue;
298
299       theSystems.Append( theSeq.Value( s ) );
300       aMap.Add( theSeq.Value( s ) );
301     }
302   }
303
304 }
305
306 /*!
307   \brief Get the names of defined units systems from the specified component
308          \a theComponent.
309
310   If component is not found, empty list is returned.
311
312   \param theSystems returning sequence of units systems names.
313   \param theComponent component name
314 */
315 void DDS_Dictionary::GetUnitSystems( TColStd_SequenceOfAsciiString& theSystems,
316                                      const TCollection_AsciiString& theComponent ) const
317 {
318   theSystems.Clear();
319   if ( myGroupMap.Contains( theComponent ) )
320     myGroupMap.FindFromKey( theComponent )->GetUnitSystems( theSystems );
321 }
322
323 /*!
324   \brief Get the label of the units system \a theSystem.
325
326   Searches the given units system in all components. If units system is not found
327   in any component, empty string is returned.
328
329   \param theSystem units system
330   \return units system label
331 */
332 TCollection_ExtendedString DDS_Dictionary::GetUnitSystemLabel( const TCollection_AsciiString& theSystem ) const
333 {
334   TCollection_ExtendedString aLabel;
335   for ( Standard_Integer i = 1; i <= myGroupMap.Extent() && !aLabel.Length(); i++ )
336     aLabel = myGroupMap.FindFromIndex( i )->GetUnitSystemLabel( theSystem );
337   return aLabel;
338 }
339
340 /*!
341   \brief Get the label of the units system \a theSystem from the
342          component \a theComponent.
343
344   Searches the specified units system in the specified component only.
345   If units system is not found, empty string is returned.
346
347   \param theSystem units system
348   \param theComponent component name
349   \return units system label
350 */
351 TCollection_ExtendedString DDS_Dictionary::GetUnitSystemLabel( const TCollection_AsciiString& theSystem,
352                                                                const TCollection_AsciiString& theComponent ) const
353 {
354   TCollection_ExtendedString aLabel;
355   if ( myGroupMap.Contains( theComponent ) )
356     aLabel = myGroupMap.FindFromKey( theComponent )->GetUnitSystemLabel( theSystem );
357   return aLabel;
358 }
359
360 /*!
361   \brief Get the name of active units system from the first found component. 
362   
363   If at least one component exists, then its active units system name 
364   is returned. Otherwise, empty string is returned.
365
366   \return active units system name
367 */
368 TCollection_AsciiString DDS_Dictionary::GetActiveUnitSystem() const
369 {
370   TCollection_AsciiString aSystem;
371   if ( myGroupMap.Extent() )
372     aSystem = myGroupMap.FindFromIndex( 1 )->GetActiveUnitSystem();
373   return aSystem;
374 }
375
376 /*!
377   \brief Get the name of active units system from the component \a theComponent.
378
379   If this component exists, its active units system name is returned.
380   Otherwise, empty string is returned.
381
382   \param theComponent component name
383   \return active units system name
384 */
385 TCollection_AsciiString DDS_Dictionary::GetActiveUnitSystem( const TCollection_AsciiString& theComponent ) const
386 {
387   TCollection_AsciiString aSystem;
388   if ( myGroupMap.Contains( theComponent ) )
389     aSystem = myGroupMap.FindFromKey( theComponent )->GetActiveUnitSystem();
390   return aSystem;
391 }
392
393 /*!
394   \brief Set the active units system. 
395
396   This units system will be activated in each existing component, 
397   if it component has this units system.
398
399   \param theSystem units system to be made active
400 */
401 void DDS_Dictionary::SetActiveUnitSystem( const TCollection_AsciiString& theSystem )
402 {
403   for ( Standard_Integer i = 1; i <= myGroupMap.Extent(); i++ )
404     myGroupMap.FindFromIndex( i )->SetActiveUnitSystem( theSystem );
405 }
406
407 /*!
408   \brief Set the active units system for the component \a theComponent. 
409
410   If specified units system doesn't exist in the component, nothing happens.
411
412   \param theSystem units system to be made active
413   \param theComponent component name
414 */
415 void DDS_Dictionary::SetActiveUnitSystem( const TCollection_AsciiString& theSystem,
416                                           const TCollection_AsciiString& theComponent )
417 {
418   if ( myGroupMap.Contains( theComponent ) )
419     myGroupMap.FindFromKey( theComponent )->SetActiveUnitSystem( theSystem );
420 }
421
422 /*!
423   \brief Get the only instance of the data dictionary.
424   \return the only instance of the data dictionary
425 */
426 Handle_DDS_Dictionary DDS_Dictionary::Get()
427 {
428   static Handle(DDS_Dictionary) sDictionary;
429
430   if ( sDictionary.IsNull() )
431     sDictionary = new DDS_Dictionary();
432
433   return sDictionary;
434 }
435
436 /*!
437   \brief Load datum definitions in the dictionary from the XML file
438   \a theFileName.
439   \param theFileName XML file name
440   \return \c true if loading is succeded or \c false otherwise.
441 */
442 Standard_Boolean DDS_Dictionary::Load( const TCollection_AsciiString theFileName )
443 {
444   static NCollection_Map<TCollection_AsciiString> _LoadMap;
445
446   if ( _LoadMap.Contains( theFileName ) )
447     return Standard_True;
448
449   Handle(DDS_Dictionary) aDic = Get();
450   if ( aDic.IsNull() )
451     return Standard_False;
452
453   LDOMParser aParser;
454   if ( aParser.parse( theFileName.ToCString() ) )
455     return Standard_False;
456
457   LDOM_Document aDoc = aParser.getDocument();
458   LDOM_Element aDocElement = aDoc.getDocumentElement();
459   for ( LDOM_Element aComponentElem = aDocElement.GetChildByTagName( KeyWord( "COMPONENT" ) );
460         !aComponentElem.isNull(); aComponentElem = aComponentElem.GetSiblingByTagName() )
461     aDic->FillDataMap( aComponentElem, aDocElement );
462
463   _LoadMap.Add( theFileName );
464
465   return Standard_True;
466 }
467
468 /*!
469   \brief Get XML keyword as LDOMString by specified \a key.
470
471   If key doesn't exist, empty string is returned. 
472   This function is provided for convenience.
473   
474   \param key keyword name
475   \return keyword value
476 */
477 LDOMString DDS_Dictionary::KeyWord( const TCollection_AsciiString& key )
478 {
479   LDOMString keyWord;
480   Handle(DDS_KeyWords) aKeyWords = DDS_KeyWords::Get();
481   if ( !aKeyWords.IsNull() )
482   {
483     TCollection_AsciiString aStr = aKeyWords->GetKeyWord( key );
484     if ( aStr.Length() )
485       keyWord = LDOMString( aStr.ToCString() );
486   }
487   return keyWord;
488 }
489
490 /*!
491   \brief Get the data dictionary item by specified identifier \a theID
492   from the component \a theComponent.
493   
494   If the component or item is not found, null handle is returned.
495   \param theID data dictionary item ID
496   \param theComponent component name
497   \return handle to the data dictionary item
498 */
499 Handle_DDS_DicItem DDS_Dictionary::GetDicItem( const TCollection_AsciiString& theID,
500                                                 const TCollection_AsciiString& theComponent ) const
501 {
502   Handle(DDS_DicItem) aDicItem;
503   Handle(DDS_DicGroup) aDicGroup;
504   if ( myGroupMap.Contains( theComponent ) )
505     aDicGroup = myGroupMap.FindFromKey( theComponent );
506   if ( !aDicGroup.IsNull() )
507     aDicItem = aDicGroup->GetDicItem( theID );
508   return aDicItem;
509 }
510
511 /*!
512   \brief Get the data dictionary item by specified identifier \a theID.
513
514   Function searches the item in all components. If item is not found
515   in all components, null handle is returned.
516
517   \param theID data dictionary item ID
518   \return handle to the data dictionary item
519 */
520 Handle_DDS_DicItem DDS_Dictionary::GetDicItem( const TCollection_AsciiString& theID ) const
521 {
522   Handle(DDS_DicItem) aDicItem;
523   for ( Standard_Integer i = 1; i <= myGroupMap.Extent() && aDicItem.IsNull(); i++ )
524     aDicItem = myGroupMap.FindFromIndex( i )->GetDicItem( theID );
525   return aDicItem;
526 }
527
528 /*!
529   \brief Fill the internal data structures from the XML node.
530   \param theComponentData component XML node
531   \param theDocElement document XML node
532 */
533 void DDS_Dictionary::FillDataMap( const LDOM_Element& theComponentData, const LDOM_Element& theDocElement )
534 {
535   TCollection_AsciiString aCompName = theComponentData.getAttribute( KeyWord( "COMPONENT_NAME" ) );
536   if ( !myGroupMap.Contains( aCompName ) )
537     myGroupMap.Add( aCompName, new DDS_DicGroup( aCompName ) );
538   Handle(DDS_DicGroup) aDicGroup = myGroupMap.FindFromKey( aCompName );
539   aDicGroup->FillDataMap( theComponentData, theDocElement );
540   myGroupMap.Add( aCompName, aDicGroup );
541 }
542
543 /*!
544   \brief Convert numeric value \a theValue from specified measure units
545   \a theUnits to "SI" measure units (mm for Length, radians for Angles, etc).
546   \param theValue value being converted
547   \param theUnits measure units
548   \return converted value
549 */
550 Standard_Real DDS_Dictionary::ToSI( const Standard_Real theValue, const Standard_CString theUnits )
551 {
552   Standard_Real aRetValue = theValue;
553   if ( theUnits && *theUnits && strcmp( theUnits, "%" ) )
554   {
555     try {
556 #if OCC_VERSION_LARGE > 0x06010000
557       OCC_CATCH_SIGNALS;
558 #endif
559       aRetValue = UnitsAPI::AnyToSI( theValue, theUnits );
560     }
561     catch( Standard_Failure ) {
562     }
563   }
564   else if ( theUnits && *theUnits )
565     aRetValue = theValue / 100.0;
566
567   return aRetValue;
568 }
569
570 /*!
571   \brief Convert numeric value \a theValue to specified measure units
572   \a theUnits from "SI" measure units (mm for Length, radians for Angles, etc).
573   \param theValue value being converted
574   \param theUnits measure units
575   \return converted value
576 */
577 Standard_Real DDS_Dictionary::FromSI( const Standard_Real theValue, const Standard_CString theUnits )
578 {
579   Standard_Real aRetValue = theValue;
580   if ( theUnits && *theUnits && strcmp( theUnits, "%" ) )
581   {
582     try {
583 #if OCC_VERSION_LARGE > 0x06010000
584       OCC_CATCH_SIGNALS;
585 #endif
586       aRetValue = UnitsAPI::AnyFromSI( theValue, theUnits );
587     }
588     catch( Standard_Failure ) {
589     }
590   }
591   else if ( theUnits && *theUnits )
592     aRetValue = theValue * 100.0;
593
594   return aRetValue;
595 }
596
597 /*!
598   \brief Fill given string container \a seq with keys belonging to group with name \a theComponent
599   \param theComponent name of group whose keys should be stored in the container
600   \param seq returned string container with keys belonging to group; it is not cleared before filling
601 */
602 void DDS_Dictionary::GetKeys( const TCollection_AsciiString& theComponent, TColStd_SequenceOfAsciiString& seq ) const
603 {
604   Handle( DDS_DicGroup ) aDicGroup;
605   if( myGroupMap.Contains( theComponent ) )
606     aDicGroup = myGroupMap.FindFromKey( theComponent );
607
608   if( !aDicGroup.IsNull() )
609     aDicGroup->GetKeys( seq );
610 }