File: classification.py

package info (click to toggle)
thuban 1.2.2-14
  • links: PTS, VCS
  • area: main
  • in suites: buster
  • size: 9,176 kB
  • sloc: python: 30,410; ansic: 6,181; xml: 4,234; cpp: 1,595; makefile: 145
file content (856 lines) | stat: -rw-r--r-- 26,447 bytes parent folder | download | duplicates (6)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
# Copyright (c) 2001, 2003, 2005, 2006 by Intevation GmbH
# Authors:
# Jonathan Coles <jonathan@intevation.de>
# Jan-Oliver Wagner <jan@intevation.de> (2005)
# Frank Koormann <frank@intevation.de> (2006)
#
# This program is free software under the GPL (>=v2)
# Read the file COPYING coming with Thuban for details.

__version__ = "$Revision: 2688 $"

"""
A Classification provides a mapping from an input value
to data. This mapping can be specified in two ways. 
First, specific values can be associated with data. 
Second, ranges can be associated with data such that if 
an input value falls with a range that data is returned. 
If no mapping can be found then default data will 
be returned. Input values must be hashable objects

See the description of FindGroup() for more information 
on the mapping algorithm.
"""
  
import copy, operator, types
import re

from Thuban import _

from messages import \
    LAYER_PROJECTION_CHANGED, \
    LAYER_LEGEND_CHANGED, \
    LAYER_VISIBILITY_CHANGED,\
    CLASS_CHANGED

from Thuban.Model.color import Color, Transparent, Black
from Thuban.Model.range import Range

import Thuban.Model.layer

from Thuban.Lib.connector import Publisher

class Classification(Publisher):
    """Encapsulates the classification of layer. 
    
    The Classification divides some kind of data into Groups which 
    are associated with properties. Later the properties can be 
    retrieved by matching data values to the appropriate group.
    """

    def __init__(self):
        """Initialize a classification."""
 
        self.__groups = []

        self.SetDefaultGroup(ClassGroupDefault())

    def __iter__(self):
        return ClassIterator(self.__groups) 

    def __deepcopy__(self, memo):
        clazz = Classification()

        clazz.__groups[0] = copy.deepcopy(self.__groups[0])

        for i in range(1, len(self.__groups)):
            clazz.__groups.append(copy.deepcopy(self.__groups[i]))

        return clazz

    def __SendNotification(self):
        """Notify the layer that this class has changed."""
        self.issue(CLASS_CHANGED)

    def __getattr__(self, attr):
        """Generate the compiled classification on demand"""
        if attr == "_compiled_classification":
            self._compile_classification()
            return self._compiled_classification
        raise AttributeError(attr)

    def _compile_classification(self):
        """Generate the compiled classification

        The compiled classification is a more compact representation of
        the classification groups that is also more efficient for
        performing the classification.

        The compiled classification is a list of tuples. The first
        element of the tuple is a string which describes the rest of the
        tuple. There are two kinds of tuples:

          'singletons'

            The second element of the tuple is a dictionary which
            combines several consecutive ClassGroupSingleton instances.
            The dictionary maps the values of the singletons (as
            returned by the GetValue() method) to the corresponding
            group.

          'range'

            The tuple describes a ClassGroupRange instance. The tuples
            second element is a tuple fo the form (lfunc, min, max,
            rfunc, group) where group is the original group object,
            lfunc and rfuct are comparison functions and min and max are
            lower and upper bounds of the range. Given a value and such
            a tuple the group matches if and only if

                lfunc(min, value) and rfunc(max, value)

            is true.

          'pattern'
            
            The tuple contains the compiled regular expression object and 
            the original group object.

        The compiled classification is bound to
        self._compile_classification.
        """
        compiled = []
        for group in self.__groups[1:]:
            if isinstance(group, ClassGroupSingleton):
                if not compiled or compiled[-1][0] != "singletons":
                    compiled.append(("singletons", {}))
                compiled[-1][1].setdefault(group.GetValue(), group)
            elif isinstance(group, ClassGroupPattern):
                pattern = re.compile(group.GetPattern())
                compiled.append(("pattern", (pattern, group)))
            elif isinstance(group, ClassGroupRange):
                left, min, max, right = group.GetRangeTuple()
                if left == "[":
                    lfunc = operator.le
                elif left == "]":
                    lfunc = operator.lt
                if right == "[":
                    rfunc = operator.gt
                elif right == "]":
                    rfunc = operator.ge
                compiled.append(("range", (lfunc, min, max, rfunc, group)))
            else:
                raise TypeError("Unknown group type %s", group)
        self._compiled_classification = compiled

    def _clear_compiled_classification(self):
        """Reset the compiled classification.

        If will be created on demand when self._compiled_classification
        is accessed again.

        Call this method whenever self.__groups is modified.
        """
        try:
            del self._compiled_classification
        except:
            pass

    #
    # these SetDefault* methods are really only provided for 
    # some backward compatibility. they should be considered
    # for removal once all the classification code is finished.
    #

    def SetDefaultFill(self, fill):
        """Set the default fill color.

        fill -- a Color object.
        """
        self.GetDefaultGroup().GetProperties().SetFill(fill)
        self.__SendNotification()
        
    def GetDefaultFill(self):
        """Return the default fill color."""
        return self.GetDefaultGroup().GetProperties().GetFill()
        
    def SetDefaultLineColor(self, color):
        """Set the default line color.

        color -- a Color object.
        """
        self.GetDefaultGroup().GetProperties().SetLineColor(color)
        self.__SendNotification()
        
    def GetDefaultLineColor(self):
        """Return the default line color."""
        return self.GetDefaultGroup().GetProperties().GetLineColor()
        
    def SetDefaultLineWidth(self, lineWidth):
        """Set the default line width.

        lineWidth -- an integer > 0.
        """
        assert isinstance(lineWidth, types.IntType)
        self.GetDefaultGroup().GetProperties().SetLineWidth(lineWidth)
        self.__SendNotification()
        
    def GetDefaultLineWidth(self):
        """Return the default line width."""
        return self.GetDefaultGroup().GetProperties().GetLineWidth()
        

    #
    # The methods that manipulate self.__groups have to be kept in
    # sync. We store the default group in index 0 to make it
    # convienent to iterate over the classification's groups, but
    # from the user's perspective the first (non-default) group is
    # at index 0 and the DefaultGroup is a special entity.
    #

    def SetDefaultGroup(self, group):
        """Set the group to be used when a value can't be classified.

        group -- group that the value maps to.
        """
        assert isinstance(group, ClassGroupDefault)
        if len(self.__groups) > 0:
            self.__groups[0] = group
        else:
            self.__groups.append(group)
        self.__SendNotification()

    def GetDefaultGroup(self):
        """Return the default group."""
        return self.__groups[0]

    def AppendGroup(self, item):
        """Append a new ClassGroup item to the classification.

        item -- this must be a valid ClassGroup object
        """

        self.InsertGroup(self.GetNumGroups(), item)

    def InsertGroup(self, index, group):
        assert isinstance(group, ClassGroup)
        self.__groups.insert(index + 1, group)
        self._clear_compiled_classification()
        self.__SendNotification()

    def RemoveGroup(self, index):
        """Remove the classification group with the given index"""
        self.__groups.pop(index + 1)
        self._clear_compiled_classification()
        self.__SendNotification()

    def ReplaceGroup(self, index, group):
        assert isinstance(group, ClassGroup)
        self.__groups[index + 1] = group
        self._clear_compiled_classification()
        self.__SendNotification()

    def GetGroup(self, index):
        return self.__groups[index + 1]

    def GetNumGroups(self):
        """Return the number of non-default groups in the classification."""
        return len(self.__groups) - 1

    def FindGroup(self, value):
        """Return the group that matches the value.

        Groups are effectively checked in the order the were added to
        the Classification.

        value -- the value to classify. If there is no mapping or value
                 is None, return the default properties
        """

        if value is not None:
            for typ, params in self._compiled_classification:
                if typ == "singletons":
                    group = params.get(value)
                    if group is not None:
                        return group
                elif typ == "range":
                    lfunc, min, max, rfunc, g = params
                    if lfunc(min, value) and rfunc(max, value):
                        return g
                elif typ == "pattern":
                    # TODO: make pattern more robust. The following chrashes 
                    # if accidently be applied on non-string columns. 
                    # Usually the UI prevents this.
                    p, g = params
                    if p.match(value):
                        return g

        return self.GetDefaultGroup()

    def GetProperties(self, value):
        """Return the properties associated with the given value.
       
        Use this function rather than Classification.FindGroup().GetProperties()
        since the returned group may be a ClassGroupMap which doesn't support
        a call to GetProperties(). 
        """

        group = self.FindGroup(value)
        if isinstance(group, ClassGroupMap):
            return group.GetPropertiesFromValue(value)
        else:
            return group.GetProperties()

    def TreeInfo(self):
        items = []

        def build_color_item(text, color):
            if color is Transparent:
                return ("%s: %s" % (text, _("None")), None)

            return ("%s: (%.3f, %.3f, %.3f)" % 
                    (text, color.red, color.green, color.blue),
                    color)

        def build_item(group, string):
            label = group.GetLabel()
            if label == "":
                label = string
            else:
                label += " (%s)" % string

            props = group.GetProperties()
            i = []
            v = props.GetLineColor()
            i.append(build_color_item(_("Line Color"), v))
            v = props.GetLineWidth()
            i.append(_("Line Width: %s") % v)

	    # Note: Size is owned by all properties, so
	    # a size will also appear where it does not
	    # make sense like for lines and polygons.
            v = props.GetSize()
            i.append(_("Size: %s") % v)

            v = props.GetFill()
            i.append(build_color_item(_("Fill"), v))
            return (label, i)

        for p in self:
            items.append(build_item(p, p.GetDisplayText()))

        return (_("Classification"), items)

class ClassIterator:
    """Allows the Groups in a Classifcation to be interated over.

    The items are returned in the following order:
        default data, singletons, ranges, maps
    """

    def __init__(self, data): #default, points, ranges, maps):
        """Constructor.

        default -- the default group

        points -- a list of singleton groups

        ranges -- a list of range groups

        maps -- a list of map groups
        """

        self.data = data 
        self.data_index = 0

    def __iter__(self):
        return self

    def next(self):
        """Return the next item."""

        if self.data_index >= len(self.data):
            raise StopIteration
        else:
            d = self.data[self.data_index]
            self.data_index += 1
            return d

class ClassGroupProperties:
    """Represents the properties of a single Classification Group.

    These are used when rendering a layer."""

    # TODO: Actually, size is only relevant for point objects.
    # Eventually it should be spearated, e.g. when introducing symbols.

    def __init__(self, props = None):
        """Constructor.

        props -- a ClassGroupProperties object. The class is copied if
                 prop is not None. Otherwise, a default set of properties
                 is created such that: line color = Black, line width = 1,
                 size = 5 and fill color = Transparent
        """

        if props is not None:
            self.SetProperties(props)
        else:
            self.SetLineColor(Black)
            self.SetLineWidth(1)
            self.SetSize(5)
            self.SetFill(Transparent)

    def SetProperties(self, props):
        """Set this class's properties to those in class props."""

        assert isinstance(props, ClassGroupProperties)
        self.SetLineColor(props.GetLineColor())
        self.SetLineWidth(props.GetLineWidth())
        self.SetSize(props.GetSize())
        self.SetFill(props.GetFill())

    def GetLineColor(self):
        """Return the line color as a Color object."""
        return self.__stroke

    def SetLineColor(self, color):
        """Set the line color.

        color -- the color of the line. This must be a Color object.
        """

        self.__stroke = color

    def GetLineWidth(self):
        """Return the line width."""
        return self.__strokeWidth

    def SetLineWidth(self, lineWidth):
        """Set the line width.

        lineWidth -- the new line width. This must be > 0.
        """
        assert isinstance(lineWidth, types.IntType)
        if (lineWidth < 1):
            raise ValueError(_("lineWidth < 1"))

        self.__strokeWidth = lineWidth

    def GetSize(self):
        """Return the size."""
        return self.__size

    def SetSize(self, size):
        """Set the size.

        size -- the new size. This must be > 0.
        """
        assert isinstance(size, types.IntType)
        if (size < 1):
            raise ValueError(_("size < 1"))

        self.__size = size

    def GetFill(self):
        """Return the fill color as a Color object."""
        return self.__fill

    def SetFill(self, fill):
        """Set the fill color.

        fill -- the color of the fill. This must be a Color object.
        """

        self.__fill = fill

    def __eq__(self, other):
        """Return true if 'props' has the same attributes as this class"""

        #
        # using 'is' over '==' results in a huge performance gain
        # in the renderer
        #
        return isinstance(other, ClassGroupProperties)   \
            and (self.__stroke is other.__stroke or      \
                 self.__stroke == other.__stroke)        \
            and (self.__fill is other.__fill or          \
                 self.__fill == other.__fill)            \
            and self.__strokeWidth == other.__strokeWidth\
            and self.__size == other.__size

    def __ne__(self, other): 
        return not self.__eq__(other)

    def __copy__(self):
        return ClassGroupProperties(self)

    def __deepcopy__(self):
        return ClassGroupProperties(self)

    def __repr__(self):
        return repr((self.__stroke, self.__strokeWidth, self.__size,
                    self.__fill))

class ClassGroup:
    """A base class for all Groups within a Classification"""

    def __init__(self, label = "", props = None, group = None):
        """Constructor.

        label -- A string representing the Group's label
        """

        if group is not None:
            self.SetLabel(copy.copy(group.GetLabel()))
            self.SetProperties(copy.copy(group.GetProperties()))
            self.SetVisible(group.IsVisible())
        else:
            self.SetLabel(label)
            self.SetProperties(props)
            self.SetVisible(True)

    def GetLabel(self):
        """Return the Group's label."""
        return self.label

    def SetLabel(self, label):
        """Set the Group's label.

        label -- a string representing the Group's label. This must
                 not be None.
        """
        assert isinstance(label, types.StringTypes)
        self.label = label

    def GetDisplayText(self):
        assert False, "GetDisplay must be overridden by subclass!"
        return ""

    def Matches(self, value):
        """Determines if this Group is associated with the given value.

        Returns False. This needs to be overridden by all subclasses.
        """
        assert False, "GetMatches must be overridden by subclass!"
        return False

    def GetProperties(self):
        """Return the properties associated with the given value."""

        return self.prop
 
    def SetProperties(self, prop):
        """Set the properties associated with this Group.

        prop -- a ClassGroupProperties object. if prop is None, 
                a default set of properties is created.
        """

        if prop is None: prop = ClassGroupProperties()
        assert isinstance(prop, ClassGroupProperties)
        self.prop = prop

    def IsVisible(self):
        return self.visible

    def SetVisible(self, visible):
        self.visible = visible

    def __eq__(self, other):
        return isinstance(other, ClassGroup) \
            and self.label == other.label \
            and self.GetProperties() == other.GetProperties()

    def __ne__(self, other):
        return not self.__eq__(other)

    def __repr__(self):
        return repr(self.label) + ", " + repr(self.GetProperties())
    
class ClassGroupSingleton(ClassGroup):
    """A Group that is associated with a single value."""

    def __init__(self, value = 0, props = None, label = "", group = None):
        """Constructor.

        value -- the associated value.

        prop -- a ClassGroupProperites object. If prop is None a default
                 set of properties is created.

        label -- a label for this group.
        """
        ClassGroup.__init__(self, label, props, group)

        self.SetValue(value)

    def __copy__(self):
        return ClassGroupSingleton(self.GetValue(), 
                                   self.GetProperties(), 
                                   self.GetLabel())

    def __deepcopy__(self, memo):
        return ClassGroupSingleton(self.GetValue(), group = self)

    def GetValue(self):
        """Return the associated value."""
        return self.__value

    def SetValue(self, value):
        """Associate this Group with the given value."""
        self.__value = value

    def Matches(self, value):
        """Determine if the given value matches the associated Group value."""

        """Returns True if the value matches, False otherwise."""

        return self.__value == value

    def GetDisplayText(self):
        label = self.GetLabel()

        if label != "": return label

        return str(self.GetValue())

    def __eq__(self, other):
        return ClassGroup.__eq__(self, other) \
            and isinstance(other, ClassGroupSingleton) \
            and self.__value == other.__value

    def __repr__(self): 
        return "(" + repr(self.__value) + ", " + ClassGroup.__repr__(self) + ")"

class ClassGroupDefault(ClassGroup):
    """The default Group. When values do not match any other
       Group within a Classification, the properties from this
       class are used."""

    def __init__(self, props = None, label = "", group = None):
        """Constructor.

        prop -- a ClassGroupProperites object. If prop is None a default
                 set of properties is created.

        label -- a label for this group.
        """

        ClassGroup.__init__(self, label, props, group)

    def __copy__(self):
        return ClassGroupDefault(self.GetProperties(), self.GetLabel())

    def __deepcopy__(self, memo):
        return ClassGroupDefault(label = self.GetLabel(), group = self)

    def Matches(self, value):
        return True

    def GetDisplayText(self):
        label = self.GetLabel()

        if label != "": return label

        return _("DEFAULT")

    def __eq__(self, other):
        return ClassGroup.__eq__(self, other) \
            and isinstance(other, ClassGroupDefault) \
            and self.GetProperties() == other.GetProperties()

    def __repr__(self): 
        return "(" + ClassGroup.__repr__(self) + ")"

class ClassGroupRange(ClassGroup):
    """A Group that represents a range of values that map to the same
       set of properties."""

    def __init__(self, _range = (0,1), props = None, label = "", group=None):
        """Constructor.

        The minumum value must be strictly less than the maximum.

        _range -- either a tuple (min, max) where min < max or
                  a Range object

        prop -- a ClassGroupProperites object. If prop is None a default
                 set of properties is created.

        label -- a label for this group.
        """

        ClassGroup.__init__(self, label, props, group)
        self.SetRange(_range)

    def __copy__(self):
        return ClassGroupRange(self.__range,
                               props = self.GetProperties(), 
                               label = self.GetLabel())

    def __deepcopy__(self, memo):
        return ClassGroupRange(copy.copy(self.__range), 
                               group = self)

    def GetMin(self):
        """Return the range's minimum value."""
        return self.__range.GetRange()[1]

    def SetMin(self, min):
        """Set the range's minimum value.
     
        min -- the new minimum. Note that this must be less than the current
               maximum value. Use SetRange() to change both min and max values.
        """
     
        self.SetRange((min, self.__range.GetRange()[2]))

    def GetMax(self):
        """Return the range's maximum value."""
        return self.__range.GetRange()[2]

    def SetMax(self, max):
        """Set the range's maximum value.
     
        max -- the new maximum. Note that this must be greater than the current
               minimum value. Use SetRange() to change both min and max values.
        """
        self.SetRange((self.__range.GetRange()[1], max))

    def SetRange(self, _range):
        """Set a new range.

        _range -- Either a tuple (min, max) where min < max or
                  a Range object.

        Raises ValueError on error.
        """

        if isinstance(_range, Range):
            self.__range = _range
        elif isinstance(_range, types.TupleType) and len(_range) == 2:
            self.__range = Range(("[", _range[0], _range[1], "["))
        else:
            raise ValueError()

    def GetRange(self):
        """Return the range as a string"""
        return self.__range.string(self.__range.GetRange())

    def GetRangeTuple(self):
        return self.__range.GetRange()

    def Matches(self, value):
        """Determine if the given value lies with the current range.

        The following check is used: min <= value < max.
        """

        return operator.contains(self.__range, value)

    def GetDisplayText(self):
        label = self.GetLabel()

        if label != "": return label

        return self.__range.string(self.__range.GetRange())

    def __eq__(self, other):
        return ClassGroup.__eq__(self, other) \
            and isinstance(other, ClassGroupRange) \
            and self.__range == other.__range

    def __repr__(self): 
        return "(" + str(self.__range) + ClassGroup.__repr__(self) + ")"

class ClassGroupPattern(ClassGroup):
    """A Group that is associated with a reg exp pattern."""

    def __init__(self, pattern = "", props = None, label = "", group = None):
        """Constructor.

        pattern -- the associated pattern.

        props   -- a ClassGroupProperites object. If props is None a default
                   set of properties is created.

        label   -- a label for this group.
        """
        ClassGroup.__init__(self, label, props, group)

        self.SetPattern(pattern)

    def __copy__(self):
        return ClassGroupPattern(self.GetPattern(), 
                                   self.GetProperties(), 
                                   self.GetLabel())

    def __deepcopy__(self, memo):
        return ClassGroupPattern(self.GetPattern(), group = self)

    def GetPattern(self):
        """Return the associated pattern."""
        return self.__pattern

    def SetPattern(self, pattern):
        """Associate this Group with the given pattern."""
        self.__pattern = pattern

    def Matches(self, pattern):
        """Check if the given pattern matches the associated Group pattern."""

        """Returns True if the value matches, False otherwise."""

        if re.match(self.__pattern, pattern):
            return True
        else:
            return False

    def GetDisplayText(self):
        label = self.GetLabel()

        if label != "": return label

        return str(self.GetPattern())

    def __eq__(self, other):
        return ClassGroup.__eq__(self, other) \
            and isinstance(other, ClassGroupPattern) \
            and self.__pattern == other.__pattern

    def __repr__(self): 
        return "(" + repr(self.__pattern) + ", " + ClassGroup.__repr__(self) + ")"

class ClassGroupMap(ClassGroup):
    """Currently, this class is not used."""

    FUNC_ID = "id"

    def __init__(self, map_type = FUNC_ID, func = None, prop = None, label=""):
        ClassGroup.__init__(self, label)

        self.map_type = map_type
        self.func = func

        if self.func is None:
            self.func = func_id

    def Map(self, value):
        return self.func(value)

    def GetProperties(self):
        return None

    def GetPropertiesFromValue(self, value):
        pass

    def GetDisplayText(self):
        return "Map: " + self.map_type

    #
    # built-in mappings
    #
    def func_id(value):
        return value