API  0.9.6
 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 
48 
62 
155 
158 
166 @implementation CPComparisonPredicate : CPPredicate
167 {
168  CPExpression _left;
169  CPExpression _right;
170 
173  unsigned int _options;
174  SEL _customSelector;
175 }
176 
177 // Constructors
185 + (CPPredicate)predicateWithLeftExpression:(CPExpression)left rightExpression:(CPExpression)right customSelector:(SEL)selector
186 {
187  return [[self alloc] initWithLeftExpression:left rightExpression:right customSelector:selector];
188 }
189 
199 + (CPPredicate)predicateWithLeftExpression:(CPExpression)left rightExpression:(CPExpression)right modifier:(CPComparisonPredicateModifier)modifier type:(int)type options:(unsigned)options
200 {
201  return [[self alloc] initWithLeftExpression:left rightExpression:right modifier:modifier type:type options:options];
202 }
203 
211 - (id)initWithLeftExpression:(CPExpression)left rightExpression:(CPExpression)right customSelector:(SEL)selector
212 {
213  self = [super init];
214 
215  if (self)
216  {
217  _left = left;
218  _right = right;
219  _modifier = CPDirectPredicateModifier;
221  _options = 0;
222  _customSelector = selector;
223  }
224 
225  return self;
226 }
227 
237 - (id)initWithLeftExpression:(CPExpression)left rightExpression:(CPExpression)right modifier:(CPComparisonPredicateModifier)modifier type:(CPPredicateOperatorType)type options:(unsigned)options
238 {
239  self = [super init];
240 
241  if (self)
242  {
243  _left = left;
244  _right = right;
245  _modifier = modifier;
246  _type = type;
247  _options = (type != CPMatchesPredicateOperatorType &&
248  type != CPLikePredicateOperatorType &&
251  type != CPInPredicateOperatorType &&
252  type != CPContainsPredicateOperatorType) ? 0 : options;
253 
254  _customSelector = NULL;
255  }
256 
257  return self;
258 }
259 
260 // Getting Information About a Comparison Predicate
265 - (CPComparisonPredicateModifier)comparisonPredicateModifier
266 {
267  return _modifier;
268 }
269 
274 - (SEL)customSelector
275 {
276  return _customSelector;
277 }
278 
283 - (CPExpression)leftExpression
284 {
285  return _left;
286 }
287 
292 - (unsigned)options
293 {
294  return _options;
295 }
296 
301 - (CPPredicateOperatorType)predicateOperatorType
302 {
303  return _type;
304 }
305 
310 - (CPExpression)rightExpression
311 {
312  return _right;
313 }
314 
315 - (CPString)predicateFormat
316 {
317  var modifier;
318 
319  switch (_modifier)
320  {
321  case CPDirectPredicateModifier: modifier = "";
322  break;
323  case CPAllPredicateModifier: modifier = "ALL ";
324  break;
325  case CPAnyPredicateModifier: modifier = "ANY ";
326  break;
327 
328  default: modifier = "";
329  break;
330  }
331 
332  var options;
333 
334  switch (_options)
335  {
336  case CPCaseInsensitivePredicateOption: options = "[c]";
337  break;
338  case CPDiacriticInsensitivePredicateOption: options = "[d]";
339  break;
341  options = "[cd]";
342  break;
343 
344  default: options = "";
345  break;
346  }
347 
348  var operator;
349 
350  switch (_type)
351  {
352  case CPLessThanPredicateOperatorType: operator = "<";
353  break;
354  case CPLessThanOrEqualToPredicateOperatorType: operator = "<=";
355  break;
356  case CPGreaterThanPredicateOperatorType: operator = ">";
357  break;
358  case CPGreaterThanOrEqualToPredicateOperatorType: operator = ">=";
359  break;
360  case CPEqualToPredicateOperatorType: operator = "==";
361  break;
362  case CPNotEqualToPredicateOperatorType: operator = "!=";
363  break;
364  case CPMatchesPredicateOperatorType: operator = "MATCHES";
365  break;
366  case CPLikePredicateOperatorType: operator = "LIKE";
367  break;
368  case CPBeginsWithPredicateOperatorType: operator = "BEGINSWITH";
369  break;
370  case CPEndsWithPredicateOperatorType: operator = "ENDSWITH";
371  break;
372  case CPInPredicateOperatorType: operator = "IN";
373  break;
374  case CPContainsPredicateOperatorType: operator = "CONTAINS";
375  break;
376  case CPCustomSelectorPredicateOperatorType: operator = CPStringFromSelector(_customSelector);
377  break;
378  }
379 
380  return [CPString stringWithFormat:@"%s%s %s%s %s",modifier,[_left description],operator,options,[_right description]];
381 }
382 
383 - (CPPredicate)predicateWithSubstitutionVariables:(CPDictionary)variables
384 {
385  var left = [_left _expressionWithSubstitutionVariables:variables],
386  right = [_right _expressionWithSubstitutionVariables:variables];
387 
389  return [CPComparisonPredicate predicateWithLeftExpression:left rightExpression:right modifier:_modifier type:_type options:_options];
390  else
392 }
393 
394 - (BOOL)isEqual:(id)anObject
395 {
396  if (self === anObject)
397  return YES;
398 
399  if (anObject.isa !== self.isa || _modifier !== [anObject comparisonPredicateModifier] || _type !== [anObject predicateOperatorType] || _options !== [anObject options] || _customSelector !== [anObject customSelector] || ![_left isEqual:[anObject leftExpression]] || ![_right isEqual:[anObject rightExpression]])
400  return NO;
401 
402  return YES;
403 }
404 
405 - (BOOL)_evaluateValue:lhs rightValue:rhs
406 {
407  var leftIsNil = (lhs == nil || [lhs isEqual:[CPNull null]]),
408  rightIsNil = (rhs == nil || [rhs isEqual:[CPNull null]]);
409 
410  if ((leftIsNil || rightIsNil) && _type != CPCustomSelectorPredicateOperatorType)
411  return (leftIsNil == rightIsNil &&
412  (_type == CPEqualToPredicateOperatorType ||
415 
416  var string_compare_options = 0;
417 
418  // left and right should be casted first [CAST()] following 10.5 rules.
419  switch (_type)
420  {
421  case CPLessThanPredicateOperatorType: return ([lhs compare:rhs] == CPOrderedAscending);
422  case CPLessThanOrEqualToPredicateOperatorType: return ([lhs compare:rhs] != CPOrderedDescending);
423  case CPGreaterThanPredicateOperatorType: return ([lhs compare:rhs] == CPOrderedDescending);
424  case CPGreaterThanOrEqualToPredicateOperatorType: return ([lhs compare:rhs] != CPOrderedAscending);
425  case CPEqualToPredicateOperatorType: return [lhs isEqual:rhs];
426  case CPNotEqualToPredicateOperatorType: return (![lhs isEqual:rhs]);
427 
428  case CPMatchesPredicateOperatorType: var commut = (_options & CPCaseInsensitivePredicateOption) ? "gi":"g";
430  {
431  lhs = lhs.stripDiacritics();
432  rhs = rhs.stripDiacritics();
433  }
434  return (new RegExp(rhs,commut)).test(lhs);
435 
436  case CPLikePredicateOperatorType: if (_options & CPDiacriticInsensitivePredicateOption)
437  {
438  lhs = lhs.stripDiacritics();
439  rhs = rhs.stripDiacritics();
440  }
441  var commut = (_options & CPCaseInsensitivePredicateOption) ? "gi":"g",
442  reg = new RegExp(rhs.escapeForRegExp(),commut);
443  return reg.test(lhs);
444 
445  case CPBeginsWithPredicateOperatorType: var range = CPMakeRange(0, MIN([lhs length], [rhs length]));
446  if (_options & CPCaseInsensitivePredicateOption)
447  string_compare_options |= CPCaseInsensitiveSearch;
448  if (_options & CPDiacriticInsensitivePredicateOption)
449  string_compare_options |= CPDiacriticInsensitiveSearch;
450  return ([lhs compare:rhs options:string_compare_options range:range] == CPOrderedSame);
451 
452  case CPEndsWithPredicateOperatorType: var range = CPMakeRange(MAX([lhs length] - [rhs length], 0), MIN([lhs length], [rhs length]));
453  if (_options & CPCaseInsensitivePredicateOption)
454  string_compare_options |= CPCaseInsensitiveSearch;
455  if (_options & CPDiacriticInsensitivePredicateOption)
456  string_compare_options |= CPDiacriticInsensitiveSearch;
457  return ([lhs compare:rhs options:string_compare_options range:range] == CPOrderedSame);
458 
459  case CPCustomSelectorPredicateOperatorType: return [lhs performSelector:_customSelector withObject:rhs];
460 
461  case CPInPredicateOperatorType: var a = lhs; // swap
462  lhs = rhs;
463  rhs = a;
464  case CPContainsPredicateOperatorType: if (![lhs isKindOfClass:[CPString class]])
465  {
466  if (![lhs respondsToSelector: @selector(objectEnumerator)])
467  [CPException raise:CPInvalidArgumentException reason:@"The left/right hand side for a CONTAINS/IN operator must be a collection or a string"];
468 
469  return [lhs containsObject:rhs];
470  }
471 
472  if (_options & CPCaseInsensitivePredicateOption)
473  string_compare_options |= CPCaseInsensitiveSearch;
474  if (_options & CPDiacriticInsensitivePredicateOption)
475  string_compare_options |= CPDiacriticInsensitiveSearch;
476 
477  return ([lhs rangeOfString:rhs options:string_compare_options].location != CPNotFound);
478 
479  case CPBetweenPredicateOperatorType: if ([rhs count] < 2)
480  [CPException raise:CPInvalidArgumentException reason:@"The right hand side for a BETWEEN operator must contain 2 objects"];
481 
482  return ([lhs compare:rhs[0]] == CPOrderedDescending && [lhs compare:rhs[1]] == CPOrderedAscending);
483 
484  default: return NO;
485  }
486 }
487 
488 - (BOOL)evaluateWithObject:(id)object
489 {
490  return [self evaluateWithObject:object substitutionVariables:nil];
491 }
492 
493 - (BOOL)evaluateWithObject:(id)object substitutionVariables:(CPDictionary)variables
494 {
495  var leftValue = [_left expressionValueWithObject:object context:variables],
496  rightValue = [_right expressionValueWithObject:object context:variables];
497 
498  if (_modifier == CPDirectPredicateModifier)
499  return [self _evaluateValue:leftValue rightValue:rightValue];
500  else
501  {
502  if (![leftValue respondsToSelector:@selector(objectEnumerator)])
503  [CPException raise:CPInvalidArgumentException reason:@"The left hand side for an ALL or ANY operator must be either a CPArray or a CPSet"];
504 
505  var e = [leftValue objectEnumerator],
506  result = (_modifier == CPAllPredicateModifier),
507  value;
508 
509  while ((value = [e nextObject]) !== nil)
510  {
511  var eval = [self _evaluateValue:value rightValue:rightValue];
512 
513  if (eval != result)
514  return eval;
515  }
516 
517  return result;
518  }
519 }
520 
521 @end
522 
524 
525 - (id)initWithCoder:(CPCoder)coder
526 {
527  self = [super init];
528  if (self != nil)
529  {
530  _left = [coder decodeObjectForKey:@"CPComparisonPredicateLeftExpression"];
531  _right = [coder decodeObjectForKey:@"CPComparisonPredicateRightExpression"];
532  _modifier = [coder decodeIntForKey:@"CPComparisonPredicateModifier"];
533  _type = [coder decodeIntForKey:@"CPComparisonPredicateType"];
534  _options = [coder decodeIntForKey:@"CPComparisonPredicateOptions"];
535  _customSelector = [coder decodeObjectForKey:@"CPComparisonPredicateCustomSelector"];
536  }
537 
538  return self;
539 }
540 
541 - (void)encodeWithCoder:(CPCoder)coder
542 {
543  [coder encodeObject:_left forKey:@"CPComparisonPredicateLeftExpression"];
544  [coder encodeObject:_right forKey:@"CPComparisonPredicateRightExpression"];
545  [coder encodeInt:_modifier forKey:@"CPComparisonPredicateModifier"];
546  [coder encodeInt:_type forKey:@"CPComparisonPredicateType"];
547  [coder encodeInt:_options forKey:@"CPComparisonPredicateOptions"];
548  [coder encodeObject:_customSelector forKey:@"CPComparisonPredicateCustomSelector"];
549 }
550 
551 @end
552 
553 var source = ['*','?','(',')','{','}','.','+','|','/',','^'],
554  dest = ['.*','.?','\\(','\\)','\\{','\\}','\\.','\\+','\\|','\\/','\\$','\\^'];
555 
556 String.prototype.escapeForRegExp = function()
557 {
558  var foundChar = false,
559  i = 0;
560 
561  for (; i < source.length; ++i)
562  {
563  if (this.indexOf(source[i]) !== -1)
564  {
565  foundChar = true;
566  break;
567  }
568  }
569 
570  if (!foundChar)
571  return this;
572 
573  var result = "";
574 
575  for (i = 0; i < this.length; ++i)
576  {
577  var sourceIndex = source.indexOf(this.charAt(i));
578  if (sourceIndex !== -1)
579  result += dest[sourceIndex];
580  else
581  result += this.charAt(i);
582  }
583 
584  return result;
585 };