Files
cappuccino/Foundation/CPPredicate/CPComparisonPredicate.j
T
Martin Carlberg f5937a6fad Fixed: CPComparisonPredicate does never evaluate to true for a predicate like 'something != nil'
An example:

var array = @[ @{@"Name": @"1"},
               @{@"Name": @"2", @"approveTime": @"Now"}
             ];

var predicate = [CPPredicate predicateWithFormat:@"approveTime != nil"];

CPLog(@"result: %@", [array filteredArrayUsingPredicate:predicate]); // result: <empty array>

If I run this in Cappuccino I get an empty array

If I run it in Cocoa on a Mac I get:

result: (
        {
        Name = 2;
        approveTime = Now;
    }
2020-01-31 14:24:29 +01:00

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) ? "gi":"g",
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;
};