mirror of
https://github.com/cappuccino/cappuccino.git
synced 2026-08-25 04:57:03 +00:00
471 lines
20 KiB
Plaintext
471 lines
20 KiB
Plaintext
/*
|
|
* CPComparisonPredicate.j
|
|
*
|
|
* Portions based on NSComparisonPredicate.m in Cocotron (http://www.cocotron.org/)
|
|
* Copyright (c) 2006-2007 Christopher J. W. Lloyd
|
|
*
|
|
* Created by cacaodev.
|
|
* Copyright 2010.
|
|
*
|
|
* This library is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Lesser General Public
|
|
* License as published by the Free Software Foundation; either
|
|
* version 2.1 of the License, or (at your option) any later version.
|
|
*
|
|
* This library is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* Lesser General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public
|
|
* License along with this library; if not, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
|
*/
|
|
|
|
@import "CPComparisonPredicate_Constants.j"
|
|
|
|
@import "CPArray.j"
|
|
@import "CPEnumerator.j"
|
|
@import "CPNull.j"
|
|
@import "CPString.j"
|
|
@import "_CPExpression.j"
|
|
@import "_CPPredicate.j"
|
|
|
|
|
|
@typedef CPComparisonPredicateModifier
|
|
@typedef CPPredicateOperatorType
|
|
|
|
/*!
|
|
@ingroup foundation
|
|
@class CPComparisonPredicate
|
|
@brief CPComparisonPredicate is a subclass of CPPredicate used to compare expressions.
|
|
|
|
Comparison predicates are predicates used to compare the results of two expressions. Comparison predicates take an operator, a left expression, and a right expression, and return as a BOOL the result of invoking the operator with the results of evaluating the expressions. Expressions are represented by instances of the CPExpression class.
|
|
*/
|
|
@implementation CPComparisonPredicate : CPPredicate
|
|
{
|
|
CPExpression _left;
|
|
CPExpression _right;
|
|
|
|
CPComparisonPredicateModifier _modifier;
|
|
CPPredicateOperatorType _type;
|
|
unsigned int _options;
|
|
SEL _customSelector;
|
|
}
|
|
|
|
// Constructors
|
|
/*!
|
|
Returns a new predicate formed by combining the left and right expressions using a given selector.
|
|
@param left The left hand side expression.
|
|
@param right The right hand side expression.
|
|
@param selector The selector to use for comparison. The method defined by the selector must take a single argument and return a BOOL value.
|
|
@return A new predicate formed by combining the left and right expressions using selector.
|
|
*/
|
|
+ (CPPredicate)predicateWithLeftExpression:(CPExpression)left rightExpression:(CPExpression)right customSelector:(SEL)selector
|
|
{
|
|
return [[self alloc] initWithLeftExpression:left rightExpression:right customSelector:selector];
|
|
}
|
|
|
|
/*!
|
|
Creates and returns a predicate of a given type formed by combining given left and right expressions using a given modifier and options.
|
|
@param left The left hand expression.
|
|
@param right The right hand expression.
|
|
@param modifier The modifier to apply.
|
|
@param type The predicate operator type.
|
|
@param options The options to apply (see CPComparisonPredicate Options).
|
|
@return A new predicate of type type formed by combining the given left and right expressions using the modifier and options.
|
|
*/
|
|
+ (CPPredicate)predicateWithLeftExpression:(CPExpression)left rightExpression:(CPExpression)right modifier:(CPComparisonPredicateModifier)modifier type:(int)type options:(unsigned)options
|
|
{
|
|
return [[self alloc] initWithLeftExpression:left rightExpression:right modifier:modifier type:type options:options];
|
|
}
|
|
|
|
/*!
|
|
Initializes a predicate formed by combining given left and right expressions using a given selector.
|
|
@param left The left hand side expression.
|
|
@param right The right hand side expression.
|
|
@param selector The selector to use for comparison. The method defined by the selector must take a single argument and return a BOOL value.
|
|
@return The receiver, initialized by combining the left and right expressions using selector.
|
|
*/
|
|
- (id)initWithLeftExpression:(CPExpression)left rightExpression:(CPExpression)right customSelector:(SEL)selector
|
|
{
|
|
self = [super init];
|
|
|
|
if (self)
|
|
{
|
|
_left = left;
|
|
_right = right;
|
|
_modifier = CPDirectPredicateModifier;
|
|
_type = CPCustomSelectorPredicateOperatorType;
|
|
_options = 0;
|
|
_customSelector = selector;
|
|
}
|
|
|
|
return self;
|
|
}
|
|
|
|
/*!
|
|
Initializes a predicate to a given type formed by combining given left and right expressions using a given modifier and options.
|
|
@param left The left hand expression.
|
|
@param right The right hand expression.
|
|
@param modifier The modifier to apply.
|
|
@param type The predicate operator type.
|
|
@param options The options to apply (see CPComparisonPredicate Options).
|
|
@return The receiver, initialized to a predicate of type type formed by combining the left and right expressions using the modifier and options.
|
|
*/
|
|
- (id)initWithLeftExpression:(CPExpression)left rightExpression:(CPExpression)right modifier:(CPComparisonPredicateModifier)modifier type:(CPPredicateOperatorType)type options:(unsigned)options
|
|
{
|
|
self = [super init];
|
|
|
|
if (self)
|
|
{
|
|
_left = left;
|
|
_right = right;
|
|
_modifier = modifier;
|
|
_type = type;
|
|
_options = (type != CPMatchesPredicateOperatorType &&
|
|
type != CPLikePredicateOperatorType &&
|
|
type != CPBeginsWithPredicateOperatorType &&
|
|
type != CPEndsWithPredicateOperatorType &&
|
|
type != CPInPredicateOperatorType &&
|
|
type != CPContainsPredicateOperatorType) ? 0 : options;
|
|
|
|
_customSelector = NULL;
|
|
}
|
|
|
|
return self;
|
|
}
|
|
|
|
// Getting Information About a Comparison Predicate
|
|
/*!
|
|
Returns the comparison predicate modifier for the receiver.
|
|
@return The comparison predicate modifier for the receiver.
|
|
*/
|
|
- (CPComparisonPredicateModifier)comparisonPredicateModifier
|
|
{
|
|
return _modifier;
|
|
}
|
|
|
|
/*!
|
|
Returns the selector for the receiver.
|
|
@return The selector for the receiver, or NULL if there is none.
|
|
*/
|
|
- (SEL)customSelector
|
|
{
|
|
return _customSelector;
|
|
}
|
|
|
|
/*!
|
|
Returns the left expression for the receiver.
|
|
@return The left expression for the receiver, or nil if there is none.
|
|
*/
|
|
- (CPExpression)leftExpression
|
|
{
|
|
return _left;
|
|
}
|
|
|
|
/*!
|
|
Returns the options that are set for the receiver.
|
|
@return The options that are set for the receiver.
|
|
*/
|
|
- (unsigned)options
|
|
{
|
|
return _options;
|
|
}
|
|
|
|
/*!
|
|
Returns the predicate type for the receiver.
|
|
@return Returns the predicate type for the receiver.
|
|
*/
|
|
- (CPPredicateOperatorType)predicateOperatorType
|
|
{
|
|
return _type;
|
|
}
|
|
|
|
/*!
|
|
Returns the right expression for the receiver.
|
|
@return The right expression for the receiver, or nil if there is none.
|
|
*/
|
|
- (CPExpression)rightExpression
|
|
{
|
|
return _right;
|
|
}
|
|
|
|
- (CPString)predicateFormat
|
|
{
|
|
var modifier;
|
|
|
|
switch (_modifier)
|
|
{
|
|
case CPDirectPredicateModifier: modifier = "";
|
|
break;
|
|
case CPAllPredicateModifier: modifier = "ALL ";
|
|
break;
|
|
case CPAnyPredicateModifier: modifier = "ANY ";
|
|
break;
|
|
|
|
default: modifier = "";
|
|
break;
|
|
}
|
|
|
|
var options;
|
|
|
|
switch (_options)
|
|
{
|
|
case CPCaseInsensitivePredicateOption: options = "[c]";
|
|
break;
|
|
case CPDiacriticInsensitivePredicateOption: options = "[d]";
|
|
break;
|
|
case CPCaseInsensitivePredicateOption | CPDiacriticInsensitivePredicateOption:
|
|
options = "[cd]";
|
|
break;
|
|
|
|
default: options = "";
|
|
break;
|
|
}
|
|
|
|
var operator;
|
|
|
|
switch (_type)
|
|
{
|
|
case CPLessThanPredicateOperatorType: operator = "<";
|
|
break;
|
|
case CPLessThanOrEqualToPredicateOperatorType: operator = "<=";
|
|
break;
|
|
case CPGreaterThanPredicateOperatorType: operator = ">";
|
|
break;
|
|
case CPGreaterThanOrEqualToPredicateOperatorType: operator = ">=";
|
|
break;
|
|
case CPEqualToPredicateOperatorType: operator = "==";
|
|
break;
|
|
case CPNotEqualToPredicateOperatorType: operator = "!=";
|
|
break;
|
|
case CPMatchesPredicateOperatorType: operator = "MATCHES";
|
|
break;
|
|
case CPLikePredicateOperatorType: operator = "LIKE";
|
|
break;
|
|
case CPBeginsWithPredicateOperatorType: operator = "BEGINSWITH";
|
|
break;
|
|
case CPEndsWithPredicateOperatorType: operator = "ENDSWITH";
|
|
break;
|
|
case CPInPredicateOperatorType: operator = "IN";
|
|
break;
|
|
case CPContainsPredicateOperatorType: operator = "CONTAINS";
|
|
break;
|
|
case CPCustomSelectorPredicateOperatorType: operator = CPStringFromSelector(_customSelector);
|
|
break;
|
|
}
|
|
|
|
return [CPString stringWithFormat:@"%s%s %s%s %s",modifier,[_left description],operator,options,[_right description]];
|
|
}
|
|
|
|
- (CPPredicate)predicateWithSubstitutionVariables:(CPDictionary)variables
|
|
{
|
|
var left = [_left _expressionWithSubstitutionVariables:variables],
|
|
right = [_right _expressionWithSubstitutionVariables:variables];
|
|
|
|
if (_type != CPCustomSelectorPredicateOperatorType)
|
|
return [CPComparisonPredicate predicateWithLeftExpression:left rightExpression:right modifier:_modifier type:_type options:_options];
|
|
else
|
|
return [CPComparisonPredicate predicateWithLeftExpression:left rightExpression:right customSelector:_customSelector];
|
|
}
|
|
|
|
- (BOOL)isEqual:(id)anObject
|
|
{
|
|
if (self === anObject)
|
|
return YES;
|
|
|
|
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]])
|
|
return NO;
|
|
|
|
return YES;
|
|
}
|
|
|
|
- (BOOL)_evaluateValue:lhs rightValue:rhs
|
|
{
|
|
var leftIsNil = (lhs == nil || [lhs isEqual:[CPNull null]]),
|
|
rightIsNil = (rhs == nil || [rhs isEqual:[CPNull null]]);
|
|
|
|
if ((leftIsNil || rightIsNil) && _type != CPCustomSelectorPredicateOperatorType)
|
|
return (leftIsNil === rightIsNil &&
|
|
(_type === CPEqualToPredicateOperatorType ||
|
|
_type === CPLessThanOrEqualToPredicateOperatorType ||
|
|
_type === CPGreaterThanOrEqualToPredicateOperatorType))
|
|
||
|
|
(leftIsNil !== rightIsNil &&
|
|
(_type === CPNotEqualToPredicateOperatorType));
|
|
|
|
var string_compare_options = 0;
|
|
|
|
// left and right should be casted first [CAST()] following 10.5 rules.
|
|
switch (_type)
|
|
{
|
|
case CPLessThanPredicateOperatorType: return ([lhs compare:rhs] == CPOrderedAscending);
|
|
case CPLessThanOrEqualToPredicateOperatorType: return ([lhs compare:rhs] != CPOrderedDescending);
|
|
case CPGreaterThanPredicateOperatorType: return ([lhs compare:rhs] == CPOrderedDescending);
|
|
case CPGreaterThanOrEqualToPredicateOperatorType: return ([lhs compare:rhs] != CPOrderedAscending);
|
|
case CPEqualToPredicateOperatorType: return [lhs isEqual:rhs];
|
|
case CPNotEqualToPredicateOperatorType: return (![lhs isEqual:rhs]);
|
|
|
|
case CPMatchesPredicateOperatorType: var commut = (_options & CPCaseInsensitivePredicateOption) ? "gi":"g";
|
|
if (_options & CPDiacriticInsensitivePredicateOption)
|
|
{
|
|
lhs = lhs.stripDiacritics();
|
|
rhs = rhs.stripDiacritics();
|
|
}
|
|
return (new RegExp(rhs,commut)).test(lhs);
|
|
|
|
case CPLikePredicateOperatorType: if (_options & CPDiacriticInsensitivePredicateOption)
|
|
{
|
|
lhs = lhs.stripDiacritics();
|
|
rhs = rhs.stripDiacritics();
|
|
}
|
|
var commut = (_options & CPCaseInsensitivePredicateOption) ? "i":"",
|
|
reg = new RegExp("^" + rhs.escapeForRegExp() + "$",commut);
|
|
return reg.test(lhs);
|
|
|
|
case CPBeginsWithPredicateOperatorType: var range = CPMakeRange(0, MIN([lhs length], [rhs length]));
|
|
if (_options & CPCaseInsensitivePredicateOption)
|
|
string_compare_options |= CPCaseInsensitiveSearch;
|
|
if (_options & CPDiacriticInsensitivePredicateOption)
|
|
string_compare_options |= CPDiacriticInsensitiveSearch;
|
|
return ([lhs compare:rhs options:string_compare_options range:range] == CPOrderedSame);
|
|
|
|
case CPEndsWithPredicateOperatorType: var range = CPMakeRange(MAX([lhs length] - [rhs length], 0), MIN([lhs length], [rhs length]));
|
|
if (_options & CPCaseInsensitivePredicateOption)
|
|
string_compare_options |= CPCaseInsensitiveSearch;
|
|
if (_options & CPDiacriticInsensitivePredicateOption)
|
|
string_compare_options |= CPDiacriticInsensitiveSearch;
|
|
return ([lhs compare:rhs options:string_compare_options range:range] == CPOrderedSame);
|
|
|
|
case CPCustomSelectorPredicateOperatorType: return [lhs performSelector:_customSelector withObject:rhs];
|
|
|
|
case CPInPredicateOperatorType: var a = lhs; // swap
|
|
lhs = rhs;
|
|
rhs = a;
|
|
case CPContainsPredicateOperatorType: if (![lhs isKindOfClass:[CPString class]])
|
|
{
|
|
if (![lhs respondsToSelector: @selector(objectEnumerator)])
|
|
[CPException raise:CPInvalidArgumentException reason:@"The left/right hand side for a CONTAINS/IN operator must be a collection or a string"];
|
|
|
|
return [lhs containsObject:rhs];
|
|
}
|
|
|
|
if (_options & CPCaseInsensitivePredicateOption)
|
|
string_compare_options |= CPCaseInsensitiveSearch;
|
|
if (_options & CPDiacriticInsensitivePredicateOption)
|
|
string_compare_options |= CPDiacriticInsensitiveSearch;
|
|
|
|
return ([lhs rangeOfString:rhs options:string_compare_options].location != CPNotFound);
|
|
|
|
case CPBetweenPredicateOperatorType: if ([rhs count] < 2)
|
|
[CPException raise:CPInvalidArgumentException reason:@"The right hand side for a BETWEEN operator must contain 2 objects"];
|
|
|
|
return ([lhs compare:rhs[0]] == CPOrderedDescending && [lhs compare:rhs[1]] == CPOrderedAscending);
|
|
|
|
default: return NO;
|
|
}
|
|
}
|
|
|
|
- (BOOL)evaluateWithObject:(id)object
|
|
{
|
|
return [self evaluateWithObject:object substitutionVariables:nil];
|
|
}
|
|
|
|
- (BOOL)evaluateWithObject:(id)object substitutionVariables:(CPDictionary)variables
|
|
{
|
|
var leftValue = [_left expressionValueWithObject:object context:variables],
|
|
rightValue = [_right expressionValueWithObject:object context:variables];
|
|
|
|
leftValue = (typeof leftValue == "boolean") ? [CPNumber numberWithBool:leftValue] : leftValue;
|
|
rightValue = (typeof rightValue == "boolean") ? [CPNumber numberWithBool:rightValue] : rightValue;
|
|
|
|
if (_modifier == CPDirectPredicateModifier)
|
|
return [self _evaluateValue:leftValue rightValue:rightValue];
|
|
else
|
|
{
|
|
if (![leftValue respondsToSelector:@selector(objectEnumerator)])
|
|
[CPException raise:CPInvalidArgumentException reason:@"The left hand side for an ALL or ANY operator must be either a CPArray or a CPSet"];
|
|
|
|
var e = [leftValue objectEnumerator],
|
|
result = (_modifier == CPAllPredicateModifier),
|
|
value;
|
|
|
|
while ((value = [e nextObject]) != nil)
|
|
{
|
|
var eval = [self _evaluateValue:value rightValue:rightValue];
|
|
|
|
if (eval != result)
|
|
return eval;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
}
|
|
|
|
@end
|
|
|
|
@implementation CPComparisonPredicate (CPCoding)
|
|
|
|
- (id)initWithCoder:(CPCoder)coder
|
|
{
|
|
self = [super init];
|
|
if (self != nil)
|
|
{
|
|
_left = [coder decodeObjectForKey:@"CPComparisonPredicateLeftExpression"];
|
|
_right = [coder decodeObjectForKey:@"CPComparisonPredicateRightExpression"];
|
|
_modifier = [coder decodeIntForKey:@"CPComparisonPredicateModifier"];
|
|
_type = [coder decodeIntForKey:@"CPComparisonPredicateType"];
|
|
_options = [coder decodeIntForKey:@"CPComparisonPredicateOptions"];
|
|
_customSelector = [coder decodeObjectForKey:@"CPComparisonPredicateCustomSelector"];
|
|
}
|
|
|
|
return self;
|
|
}
|
|
|
|
- (void)encodeWithCoder:(CPCoder)coder
|
|
{
|
|
[coder encodeObject:_left forKey:@"CPComparisonPredicateLeftExpression"];
|
|
[coder encodeObject:_right forKey:@"CPComparisonPredicateRightExpression"];
|
|
[coder encodeInt:_modifier forKey:@"CPComparisonPredicateModifier"];
|
|
[coder encodeInt:_type forKey:@"CPComparisonPredicateType"];
|
|
[coder encodeInt:_options forKey:@"CPComparisonPredicateOptions"];
|
|
[coder encodeObject:_customSelector forKey:@"CPComparisonPredicateCustomSelector"];
|
|
}
|
|
|
|
@end
|
|
|
|
var source = ['*','?','(',')','{','}','.','+','|','/','$','^'],
|
|
dest = ['.*','.?','\\(','\\)','\\{','\\}','\\.','\\+','\\|','\\/','\\$','\\^'];
|
|
|
|
String.prototype.escapeForRegExp = function()
|
|
{
|
|
var foundChar = false,
|
|
i = 0;
|
|
|
|
for (; i < source.length; ++i)
|
|
{
|
|
if (this.indexOf(source[i]) !== -1)
|
|
{
|
|
foundChar = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!foundChar)
|
|
return this;
|
|
|
|
var result = "";
|
|
|
|
for (i = 0; i < this.length; ++i)
|
|
{
|
|
var sourceIndex = source.indexOf(this.charAt(i));
|
|
if (sourceIndex !== -1)
|
|
result += dest[sourceIndex];
|
|
else
|
|
result += this.charAt(i);
|
|
}
|
|
|
|
return result;
|
|
};
|