root/CPS3/products/CPSDirectory/trunk/utils.py

Revision 50848, 5.9 kB (checked in by jrosa, 2 years ago)

Test for #1805 and detection of type in _findType method

  • Property svn:eol-style set to native
  • Property svn:keywords set to Id
Line 
1 #-*- coding=iso-8859-15 -*-
2 # (C) Copyright 2004 Nuxeo SARL <http://nuxeo.com>
3 # Author: Georges Racinet <gracinet@nuxeo.com>
4 #
5 # This program is free software; you can redistribute it and/or modify
6 # it under the terms of the GNU General Public License version 2 as published
7 # by the Free Software Foundation.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12 # GNU General Public License for more details.
13 #
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
17 # 02111-1307, USA.
18 #
19 # $Id$
20 """CPSDirectory.utils: a module that provides utilities to be
21 used by CPSDirectory classes. """
22
23 from types import ListType, TupleType, StringType
24 import re
25 import operator
26 from types import NoneType
27 from DateTime import DateTime
28
29 from Products.CPSUtil.text import toAscii
30
31 def operator_in(a, b):
32     # operator.contains with reversed operands
33     return a in b
34
35 def operator_notin(a, b):
36     return a not in b
37
38
39 class QueryMatcher(object):
40     """ Hold/prepare a query and allow to match entries against it.
41
42     >>> qm = QueryMatcher({'id' : 'foo', 'spam' : 'eggs'},
43     ...                   accepted_keys=['id'])
44     >>> qm.match({'id':'foo'})
45     True
46
47     >>> qm = QueryMatcher({'id' : 'foo', 'spam' : 'eggs'},
48     ...                   accepted_keys=['id'])
49     >>> qm.match({'id':'the Foo'})
50     False
51
52     >>> qm = QueryMatcher({'id' : 'foo'}, accepted_keys=['id'],
53     ...                    substring_keys=['id'])
54     >>> qm.match({'id':'SpamFooEggs'})
55     True
56
57     Substring behaviour and accents:
58     >>> qm = QueryMatcher({'id' : 'fooe'}, accepted_keys=['id'],
59     ...                    substring_keys=['id'])
60     >>> qm.match({'id':'SpamFooÉggs'})
61     True
62
63     >>> qm = QueryMatcher({'id' : 'fooé'}, accepted_keys=['id'],
64     ...                    substring_keys=['id'])
65     >>> qm.match({'id':'SpamFooEggs'})
66     True
67
68     >>> qm = QueryMatcher({'enabled': False}, accepted_keys=['enabled'])
69     >>> qm.match({'enabled': True})
70     False
71     >>> qm.match({'enabled': False})
72     True
73
74     >>> qm = QueryMatcher({'enabled': True}, accepted_keys=['enabled'])
75     >>> qm.match({'enabled': True})
76     True
77     >>> qm.match({'enabled': False})
78     False
79
80     We don't fail if the entry lacks keys from the query
81     >>> qm.match({})
82     False
83
84     """
85
86     def __init__(self, query, accepted_keys=None, substring_keys=None):
87         self._substring_keys = substring_keys
88         _query = {}
89         ops = {}
90         for key, value in query.items():
91             if accepted_keys is not None and key not in accepted_keys:
92                 continue
93             value, op = self._findType(key, value)
94             if op is None:
95                 continue
96             ops[key] = op
97             _query[key] = value
98         self.query = _query
99         self.ops = ops
100
101     def _findType(self, key, value, negate=False):
102         """Find op and value.
103         """
104         if value in ('', None): # XXX
105             if negate:
106                 raise NotImplementedError
107             # Ignore empty searches
108             return value, None
109         elif isinstance(value, basestring):
110             if (self._substring_keys is not None
111                 and key in self._substring_keys):
112                 if negate:
113                     raise NotImplementedError
114                 op = 'substring'
115                 value = value.lower()
116             else:
117                 if negate:
118                     op = operator.ne
119                 else:
120                     op = operator.eq
121         elif isinstance(value, (int, long, bool)):
122             if negate:
123                 op = operator.ne
124             else:
125                 op = operator.eq
126         elif isinstance(value, (list, tuple)):
127             if negate:
128                 op = operator_notin
129             else:
130                 op = operator_in
131         elif isinstance(value, dict) and 'query' in value:
132             if negate and value.get('negate'):
133                 raise ValueError("Cannot double negate")
134             query = value['query']
135             return self._findType(key, query, negate=True)
136         elif isinstance(value, DateTime):
137             if negate:
138                 op = operator.ne
139             else:
140                 op = operator.eq
141         else:
142             raise ValueError("Bad value %s for '%s'" % (`value`, key))
143         return value, op
144
145     def getKeysSet(self):
146         return set(self.query)
147
148     def match(self, entry):
149         """ Does the entry match the query ? Boolean valued.
150         """
151         ops = self.ops
152         ok = True
153         for key, value in self.query.items():
154             if key not in entry:
155                 ok = False
156                 break
157             searched = entry[key]
158             if isinstance(searched, (basestring, int, long, NoneType, DateTime)):
159                 # bool subclasses int
160                 searched = (searched,)
161             matched = 0
162             value_re = None
163             op = ops[key]
164             if isinstance(value, basestring):
165                 if op == 'substring':
166                     value = toAscii(value).lower()
167                 if '*' in value or '?' in value:
168                     regexp = re.escape(value)
169                     regexp = regexp.replace('\\?', '.?')
170                     regexp = regexp.replace('\\*', '.*')
171                     value_re = re.compile(regexp)
172
173             for item in searched:
174                 # Wild cards like * are currently accepted
175                 if op == 'substring':
176                     matched = value in toAscii(item).lower() or value == '*'
177                 else: # op is an operator
178                     matched = op(item, value)
179                 if matched:
180                     break
181             if not matched:
182                 ok = False
183                 break
184         return ok
Note: See TracBrowser for help on using the browser.