API  0.9.7
 All Classes Files Functions Variables Macros Groups Pages
CPComparisonPredicate.j
Go to the documentation of this file.
1 /*
2  * CPComparisonPredicate.j
3  *
4  * Portions based on NSComparisonPredicate.m in Cocotron (http://www.cocotron.org/)
5  * Copyright (c) 2006-2007 Christopher J. W. Lloyd
6  *
7  * Created by cacaodev.
8  * Copyright 2010.
9  *
10  * This library is free software; you can redistribute it and/or
11  * modify it under the terms of the GNU Lesser General Public
12  * License as published by the Free Software Foundation; either
13  * version 2.1 of the License, or (at your option) any later version.
14  *
15  * This library is distributed in the hope that it will be useful,
16  * but WITHOUT ANY WARRANTY; without even the implied warranty of
17  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
18  * Lesser General Public License for more details.
19  *
20  * You should have received a copy of the GNU Lesser General Public
21  * License along with this library; if not, write to the Free Software
22  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
23  */
24 
25 
26 
27 
30 
38 @implementation CPComparisonPredicate : CPPredicate
39 {
40  CPExpression _left;
41  CPExpression _right;
42 
45  unsigned int _options;
46  SEL _customSelector;
47 }
48 
49 // Constructors
57 + (CPPredicate)predicateWithLeftExpression:(CPExpression)left rightExpression:(CPExpression)right customSelector:(SEL)selector
58 {
59  return [[self alloc] initWithLeftExpression:left rightExpression:right customSelector:selector];
60 }
61 
71 + (CPPredicate)predicateWithLeftExpression:(CPExpression)left rightExpression:(CPExpression)right modifier:(CPComparisonPredicateModifier)modifier type:(int)type options:(unsigned)options
72 {
73  return [[self alloc] initWithLeftExpression:left rightExpression:right modifier:modifier type:type options:options];
74 }
75 
83 - (id)initWithLeftExpression:(CPExpression)left rightExpression:(CPExpression)right customSelector:(SEL)selector
84 {
85  self = [super init];
86 
87  if (self)
88  {
89  _left = left;
90  _right = right;
91  _modifier = CPDirectPredicateModifier;
93  _options = 0;
94  _customSelector = selector;
95  }
96 
97  return self;
98 }
99 
109 - (id)initWithLeftExpression:(CPExpression)left rightExpression:(CPExpression)right modifier:(CPComparisonPredicateModifier)modifier type:(CPPredicateOperatorType)type options:(unsigned)options
110 {
111  self = [super init];
112 
113  if (self)
114  {
115  _left = left;
116  _right = right;
117  _modifier = modifier;
118  _type = type;
119  _options = (type != CPMatchesPredicateOperatorType &&
120  type != CPLikePredicateOperatorType &&
123  type != CPInPredicateOperatorType &&
124  type != CPContainsPredicateOperatorType) ? 0 : options;
125 
126  _customSelector = NULL;
127  }
128 
129  return self;
130 }
131 
132 // Getting Information About a Comparison Predicate
137 - (CPComparisonPredicateModifier)comparisonPredicateModifier
138 {
139  return _modifier;
140 }
141 
146 - (SEL)customSelector
147 {
148  return _customSelector;
149 }
150 
155 - (CPExpression)leftExpression
156 {
157  return _left;
158 }
159 
164 - (unsigned)options
165 {
166  return _options;
167 }
168 
173 - (CPPredicateOperatorType)predicateOperatorType
174 {
175  return _type;
176 }
177 
182 - (CPExpression)rightExpression
183 {
184  return _right;
185 }
186 
187 - (CPString)predicateFormat
188 {
189  var modifier;
190 
191  switch (_modifier)
192  {
193  case CPDirectPredicateModifier: modifier = "";
194  break;
195  case CPAllPredicateModifier: modifier = "ALL ";
196  break;
197  case CPAnyPredicateModifier: modifier = "ANY ";
198  break;
199 
200  default: modifier = "";
201  break;
202  }
203 
204  var options;
205 
206  switch (_options)
207  {
208  case CPCaseInsensitivePredicateOption: options = "[c]";
209  break;
210  case CPDiacriticInsensitivePredicateOption: options = "[d]";
211  break;
213  options = "[cd]";
214  break;
215 
216  default: options = "";
217  break;
218  }
219 
220  var operator;
221 
222  switch (_type)
223  {
224  case CPLessThanPredicateOperatorType: operator = "<";
225  break;
226  case CPLessThanOrEqualToPredicateOperatorType: operator = "<=";
227  break;
228  case CPGreaterThanPredicateOperatorType: operator = ">";
229  break;
230  case CPGreaterThanOrEqualToPredicateOperatorType: operator = ">=";
231  break;
232  case CPEqualToPredicateOperatorType: operator = "==";
233  break;
234  case CPNotEqualToPredicateOperatorType: operator = "!=";
235  break;
236  case CPMatchesPredicateOperatorType: operator = "MATCHES";
237  break;
238  case CPLikePredicateOperatorType: operator = "LIKE";
239  break;
240  case CPBeginsWithPredicateOperatorType: operator = "BEGINSWITH";
241  break;
242  case CPEndsWithPredicateOperatorType: operator = "ENDSWITH";
243  break;
244  case CPInPredicateOperatorType: operator = "IN";
245  break;
246  case CPContainsPredicateOperatorType: operator = "CONTAINS";
247  break;
248  case CPCustomSelectorPredicateOperatorType: operator = CPStringFromSelector(_customSelector);
249  break;
250  }
251 
252  return [CPString stringWithFormat:@"%s%s %s%s %s",modifier,[_left description],operator,options,[_right description]];
253 }
254 
255 - (CPPredicate)predicateWithSubstitutionVariables:(CPDictionary)variables
256 {
257  var left = [_left _expressionWithSubstitutionVariables:variables],
258  right = [_right _expressionWithSubstitutionVariables:variables];
259 
261  return [CPComparisonPredicate predicateWithLeftExpression:left rightExpression:right modifier:_modifier type:_type options:_options];
262  else
264 }
265 
266 - (BOOL)isEqual:(id)anObject
267 {
268  if (self === anObject)
269  return YES;
270 
271  if (anObject === nil || anObject.isa !== self.isa || _modifier !== [anObject comparisonPredicateModifier] || _type !== [anObject predicateOperatorType] || _options !== [anObject options] || _customSelector !== [anObject customSelector] || ![_left isEqual:[anObject leftExpression]] || ![_right isEqual:[anObject rightExpression]])
272  return NO;
273 
274  return YES;
275 }
276 
277 - (BOOL)_evaluateValue:lhs rightValue:rhs
278 {
279  var leftIsNil = (lhs == nil || [lhs isEqual:[CPNull null]]),
280  rightIsNil = (rhs == nil || [rhs isEqual:[CPNull null]]);
281 
282  if ((leftIsNil || rightIsNil) && _type != CPCustomSelectorPredicateOperatorType)
283  return (leftIsNil == rightIsNil &&
284  (_type == CPEqualToPredicateOperatorType ||
287 
288  var string_compare_options = 0;
289 
290  // left and right should be casted first [CAST()] following 10.5 rules.
291  switch (_type)
292  {
293  case CPLessThanPredicateOperatorType: return ([lhs compare:rhs] == CPOrderedAscending);
294  case CPLessThanOrEqualToPredicateOperatorType: return ([lhs compare:rhs] != CPOrderedDescending);
295  case CPGreaterThanPredicateOperatorType: return ([lhs compare:rhs] == CPOrderedDescending);
296  case CPGreaterThanOrEqualToPredicateOperatorType: return ([lhs compare:rhs] != CPOrderedAscending);
297  case CPEqualToPredicateOperatorType: return [lhs isEqual:rhs];
298  case CPNotEqualToPredicateOperatorType: return (![lhs isEqual:rhs]);
299 
300  case CPMatchesPredicateOperatorType: var commut = (_options & CPCaseInsensitivePredicateOption) ? "gi":"g";
302  {
303  lhs = lhs.stripDiacritics();
304  rhs = rhs.stripDiacritics();
305  }
306  return (new RegExp(rhs,commut)).test(lhs);
307 
308  case CPLikePredicateOperatorType: if (_options & CPDiacriticInsensitivePredicateOption)
309  {
310  lhs = lhs.stripDiacritics();
311  rhs = rhs.stripDiacritics();
312  }
313  var commut = (_options & CPCaseInsensitivePredicateOption) ? "gi":"g",
314  reg = new RegExp(rhs.escapeForRegExp(),commut);
315  return reg.test(lhs);
316 
317  case CPBeginsWithPredicateOperatorType: var range = CPMakeRange(0, MIN([lhs length], [rhs length]));
318  if (_options & CPCaseInsensitivePredicateOption)
319  string_compare_options |= CPCaseInsensitiveSearch;
320  if (_options & CPDiacriticInsensitivePredicateOption)
321  string_compare_options |= CPDiacriticInsensitiveSearch;
322  return ([lhs compare:rhs options:string_compare_options range:range] == CPOrderedSame);
323 
324  case CPEndsWithPredicateOperatorType: var range = CPMakeRange(MAX([lhs length] - [rhs length], 0), MIN([lhs length], [rhs length]));
325  if (_options & CPCaseInsensitivePredicateOption)
326  string_compare_options |= CPCaseInsensitiveSearch;
327  if (_options & CPDiacriticInsensitivePredicateOption)
328  string_compare_options |= CPDiacriticInsensitiveSearch;
329  return ([lhs compare:rhs options:string_compare_options range:range] == CPOrderedSame);
330 
331  case CPCustomSelectorPredicateOperatorType: return [lhs performSelector:_customSelector withObject:rhs];
332 
333  case CPInPredicateOperatorType: var a = lhs; // swap
334  lhs = rhs;
335  rhs = a;
336  case CPContainsPredicateOperatorType: if (![lhs isKindOfClass:[CPString class]])
337  {
338  if (![lhs respondsToSelector: @selector(objectEnumerator)])
339  [CPException raise:CPInvalidArgumentException reason:@"The left/right hand side for a CONTAINS/IN operator must be a collection or a string"];
340 
341  return [lhs containsObject:rhs];
342  }
343 
344  if (_options & CPCaseInsensitivePredicateOption)
345  string_compare_options |= CPCaseInsensitiveSearch;
346  if (_options & CPDiacriticInsensitivePredicateOption)
347  string_compare_options |= CPDiacriticInsensitiveSearch;
348 
349  return ([lhs rangeOfString:rhs options:string_compare_options].location != CPNotFound);
350 
351  case CPBetweenPredicateOperatorType: if ([rhs count] < 2)
352  [CPException raise:CPInvalidArgumentException reason:@"The right hand side for a BETWEEN operator must contain 2 objects"];
353 
354  return ([lhs compare:rhs[0]] == CPOrderedDescending && [lhs compare:rhs[1]] == CPOrderedAscending);
355 
356  default: return NO;
357  }
358 }
359 
360 - (BOOL)evaluateWithObject:(id)object
361 {
362  return [self evaluateWithObject:object substitutionVariables:nil];
363 }
364 
365 - (BOOL)evaluateWithObject:(id)object substitutionVariables:(CPDictionary)variables
366 {
367  var leftValue = [_left expressionValueWithObject:object context:variables],
368  rightValue = [_right expressionValueWithObject:object context:variables];
369 
370  if (_modifier == CPDirectPredicateModifier)
371  return [self _evaluateValue:leftValue rightValue:rightValue];
372  else
373  {
374  if (![leftValue respondsToSelector:@selector(objectEnumerator)])
375  [CPException raise:CPInvalidArgumentException reason:@"The left hand side for an ALL or ANY operator must be either a CPArray or a CPSet"];
376 
377  var e = [leftValue objectEnumerator],
378  result = (_modifier == CPAllPredicateModifier),
379  value;
380 
381  while ((value = [e nextObject]) !== nil)
382  {
383  var eval = [self _evaluateValue:value rightValue:rightValue];
384 
385  if (eval != result)
386  return eval;
387  }
388 
389  return result;
390  }
391 }
392 
393 @end
394 
396 
397 - (id)initWithCoder:(CPCoder)coder
398 {
399  self = [super init];
400  if (self != nil)
401  {
402  _left = [coder decodeObjectForKey:@"CPComparisonPredicateLeftExpression"];
403  _right = [coder decodeObjectForKey:@"CPComparisonPredicateRightExpression"];
404  _modifier = [coder decodeIntForKey:@"CPComparisonPredicateModifier"];
405  _type = [coder decodeIntForKey:@"CPComparisonPredicateType"];
406  _options = [coder decodeIntForKey:@"CPComparisonPredicateOptions"];
407  _customSelector = [coder decodeObjectForKey:@"CPComparisonPredicateCustomSelector"];
408  }
409 
410  return self;
411 }
412 
413 - (void)encodeWithCoder:(CPCoder)coder
414 {
415  [coder encodeObject:_left forKey:@"CPComparisonPredicateLeftExpression"];
416  [coder encodeObject:_right forKey:@"CPComparisonPredicateRightExpression"];
417  [coder encodeInt:_modifier forKey:@"CPComparisonPredicateModifier"];
418  [coder encodeInt:_type forKey:@"CPComparisonPredicateType"];
419  [coder encodeInt:_options forKey:@"CPComparisonPredicateOptions"];
420  [coder encodeObject:_customSelector forKey:@"CPComparisonPredicateCustomSelector"];
421 }
422 
423 @end
424 
425 var source = ['*','?','(',')','{','}','.','+','|','/',','^'],
426  dest = ['.*','.?','\\(','\\)','\\{','\\}','\\.','\\+','\\|','\\/','\\$','\\^'];
427 
428 String.prototype.escapeForRegExp = function()
429 {
430  var foundChar = false,
431  i = 0;
432 
433  for (; i < source.length; ++i)
434  {
435  if (this.indexOf(source[i]) !== -1)
436  {
437  foundChar = true;
438  break;
439  }
440  }
441 
442  if (!foundChar)
443  return this;
444 
445  var result = "";
446 
447  for (i = 0; i < this.length; ++i)
448  {
449  var sourceIndex = source.indexOf(this.charAt(i));
450  if (sourceIndex !== -1)
451  result += dest[sourceIndex];
452  else
453  result += this.charAt(i);
454  }
455 
456  return result;
457 };