[ios, macos] Convert predicate options to collators

This commit is contained in:
Minh Nguyễn
2018-07-05 15:08:24 -07:00
committed by Minh Nguyễn
parent d6a454ba8e
commit f11ab91fc4
10 changed files with 149 additions and 28 deletions

View File

@@ -331,6 +331,7 @@ In style specification | Method, function, or predicate type | Format string syn
-----------------------|-------------------------------------|---------------------
`array` | |
`boolean` | |
`collator` | `NSComparisonPredicateOptions` | `'Québec' =[cd] 'QUEBEC'`
`literal` | `+[NSExpression expressionForConstantValue:]` | `%@` representing `NSArray` or `NSDictionary`
`number` | |
`string` | |

View File

@@ -53,9 +53,17 @@ The following aggregate operators are supported:
`NSInPredicateOperatorType` | `key IN { 'iOS', 'macOS', 'tvOS', 'watchOS' }`
`NSContainsPredicateOperatorType` | `{ 'iOS', 'macOS', 'tvOS', 'watchOS' } CONTAINS key`
Operator modifiers such as `c` (for case insensitivity), `d` (for diacritic
insensitivity), and `l` (for locale sensitivity) are unsupported for comparison
and aggregate operators that are used in the predicate.
The following comparison predicate options are supported for comparison and
aggregate operators that are used in the predicate:
`NSComparisonPredicateOptions` | Format string syntax
----------------------------------------|---------------------
`NSCaseInsensitivePredicateOption` | `'QUEBEC' =[c] 'Quebec'`
`NSDiacriticInsensitivePredicateOption` | `'Québec' =[d] 'Quebec'`
Other comparison predicate options are unsupported, namely `l`
(for locale sensitivity) and `n` (for normalization). A comparison is
locale-sensitive as long as it is case- or diacritic-insensitive.
### Operands

View File

@@ -140,10 +140,18 @@
[NSException raise:NSInvalidArgumentException
format:@"NSPredicateOperatorType:%lu is not supported.", (unsigned long)self.predicateOperatorType];
}
if (op) {
return @[op, self.leftExpression.mgl_jsonExpressionObject, self.rightExpression.mgl_jsonExpressionObject];
if (!op) {
return nil;
}
return nil;
NSArray *comparisonArray = @[op, self.leftExpression.mgl_jsonExpressionObject, self.rightExpression.mgl_jsonExpressionObject];
if (self.options) {
NSDictionary *collatorObject = @{
@"case-sensitive": @(!(self.options & NSCaseInsensitivePredicateOption)),
@"diacritic-sensitive": @(!(self.options & NSDiacriticInsensitivePredicateOption)),
};
return [comparisonArray arrayByAddingObject:@[@"collator", collatorObject]];
}
return comparisonArray;
}
@end

View File

@@ -784,6 +784,9 @@ NSArray *MGLSubexpressionsWithJSONObjects(NSArray *objects) {
return [NSExpression expressionForFunction:functionName
arguments:subexpressions];
} else if ([op isEqualToString:@"collator"]) {
// Avoid wrapping collator options object in literal expression.
return [NSExpression expressionForFunction:@"MGL_FUNCTION" arguments:array];
} else if ([op isEqualToString:@"literal"]) {
if ([argumentObjects.firstObject isKindOfClass:[NSArray class]]) {
return [NSExpression expressionForAggregate:MGLSubexpressionsWithJSONObjects(argumentObjects.firstObject)];
@@ -1208,6 +1211,12 @@ NSArray *MGLSubexpressionsWithJSONObjects(NSArray *objects) {
[NSException raise:NSInvalidArgumentException
format:@"Casting expression to %@ not yet implemented.", type];
} else if ([function isEqualToString:@"MGL_FUNCTION"]) {
NSExpression *op = self.arguments.firstObject;
if (op.expressionType == NSConstantValueExpressionType
&& [op.constantValue isEqualToString:@"collator"]) {
// Avoid wrapping collator options object in literal expression.
return @[@"collator", self.arguments[1].constantValue];
}
return self.arguments.mgl_jsonExpressionObject;
} else if (op == [MGLColor class] && [function isEqualToString:@"colorWithRed:green:blue:alpha:"]) {
NSArray *arguments = self.arguments.mgl_jsonExpressionObject;

View File

@@ -46,6 +46,15 @@ NSArray *MGLSubpredicatesWithJSONObjects(NSArray *objects) {
return subpredicates;
}
static NSDictionary * const MGLPredicateOperatorTypesByJSONOperator = @{
@"==": @(NSEqualToPredicateOperatorType),
@"!=": @(NSNotEqualToPredicateOperatorType),
@"<": @(NSLessThanPredicateOperatorType),
@"<=": @(NSLessThanOrEqualToPredicateOperatorType),
@">": @(NSGreaterThanPredicateOperatorType),
@">=": @(NSGreaterThanOrEqualToPredicateOperatorType),
};
+ (instancetype)predicateWithMGLJSONObject:(id)object {
if ([object isEqual:@YES]) {
return [NSPredicate predicateWithValue:YES];
@@ -58,30 +67,37 @@ NSArray *MGLSubpredicatesWithJSONObjects(NSArray *objects) {
NSArray *objects = (NSArray *)object;
NSString *op = objects.firstObject;
if ([op isEqualToString:@"=="]) {
NSNumber *operatorTypeNumber = MGLPredicateOperatorTypesByJSONOperator[op];
if (operatorTypeNumber) {
NSPredicateOperatorType operatorType = (NSPredicateOperatorType)[operatorTypeNumber unsignedIntegerValue];
NSComparisonPredicateOptions options = 0;
if (objects.count > 3) {
NSArray *collatorExpression = objects[3];
NSCAssert([collatorExpression isKindOfClass:[NSArray class]], @"Collators must be dictionaries.");
NSCAssert(collatorExpression.count == 2, @"Malformed collator expression");
NSDictionary *collator = collatorExpression[1];
NSCAssert([collator isKindOfClass:[NSDictionary class]], @"Malformed collator in collator expression");
// Predicate options can’t express specific locales as collators can.
if (!collator[@"locale"]) {
if ([(collator[@"case-sensitive"] ?: @YES) isEqual:@NO]) {
options |= NSCaseInsensitivePredicateOption;
}
if ([(collator[@"diacritic-sensitive"] ?: @YES) isEqual:@NO]) {
options |= NSDiacriticInsensitivePredicateOption;
}
}
}
NSArray *subexpressions = MGLSubexpressionsWithJSONObjects([objects subarrayWithRange:NSMakeRange(1, objects.count - 1)]);
return [NSPredicate predicateWithFormat:@"%@ == %@" argumentArray:subexpressions];
}
if ([op isEqualToString:@"!="]) {
NSArray *subexpressions = MGLSubexpressionsWithJSONObjects([objects subarrayWithRange:NSMakeRange(1, objects.count - 1)]);
return [NSPredicate predicateWithFormat:@"%@ != %@" argumentArray:subexpressions];
}
if ([op isEqualToString:@"<"]) {
NSArray *subexpressions = MGLSubexpressionsWithJSONObjects([objects subarrayWithRange:NSMakeRange(1, objects.count - 1)]);
return [NSPredicate predicateWithFormat:@"%@ < %@" argumentArray:subexpressions];
}
if ([op isEqualToString:@"<="]) {
NSArray *subexpressions = MGLSubexpressionsWithJSONObjects([objects subarrayWithRange:NSMakeRange(1, objects.count - 1)]);
return [NSPredicate predicateWithFormat:@"%@ <= %@" argumentArray:subexpressions];
}
if ([op isEqualToString:@">"]) {
NSArray *subexpressions = MGLSubexpressionsWithJSONObjects([objects subarrayWithRange:NSMakeRange(1, objects.count - 1)]);
return [NSPredicate predicateWithFormat:@"%@ > %@" argumentArray:subexpressions];
}
if ([op isEqualToString:@">="]) {
NSArray *subexpressions = MGLSubexpressionsWithJSONObjects([objects subarrayWithRange:NSMakeRange(1, objects.count - 1)]);
return [NSPredicate predicateWithFormat:@"%@ >= %@" argumentArray:subexpressions];
return [NSComparisonPredicate predicateWithLeftExpression:subexpressions[0]
rightExpression:subexpressions[1]
modifier:NSDirectPredicateModifier
type:operatorType
options:options];
}
if ([op isEqualToString:@"!"]) {
NSArray *subpredicates = MGLSubpredicatesWithJSONObjects([objects subarrayWithRange:NSMakeRange(1, objects.count - 1)]);
if (subpredicates.count > 1) {

View File

@@ -280,6 +280,79 @@
}
}
- (void)testComparisonPredicatesWithOptions {
{
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"a =[c] 'b'"];
NSArray *jsonExpression = @[@"==", @[@"get", @"a"], @"b", @[@"collator", @{@"case-sensitive": @NO, @"diacritic-sensitive": @YES}]];
XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, jsonExpression);
[self testSymmetryWithPredicate:predicate
mustRoundTrip:NO];
}
{
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"a =[d] 'b'"];
NSArray *jsonExpression = @[@"==", @[@"get", @"a"], @"b", @[@"collator", @{@"case-sensitive": @YES, @"diacritic-sensitive": @NO}]];
XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, jsonExpression);
[self testSymmetryWithPredicate:predicate
mustRoundTrip:NO];
}
{
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"a =[cd] 'b'"];
NSArray *jsonExpression = @[@"==", @[@"get", @"a"], @"b", @[@"collator", @{@"case-sensitive": @NO, @"diacritic-sensitive": @NO}]];
XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, jsonExpression);
[self testSymmetryWithPredicate:predicate
mustRoundTrip:NO];
}
{
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"a !=[cd] 'b'"];
NSArray *jsonExpression = @[@"!=", @[@"get", @"a"], @"b", @[@"collator", @{@"case-sensitive": @NO, @"diacritic-sensitive": @NO}]];
XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, jsonExpression);
[self testSymmetryWithPredicate:predicate
mustRoundTrip:NO];
}
{
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"CAST(a, 'NSString') <[cd] 'b'"];
NSArray *jsonExpression = @[@"<", @[@"to-string", @[@"get", @"a"]], @"b", @[@"collator", @{@"case-sensitive": @NO, @"diacritic-sensitive": @NO}]];
XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, jsonExpression);
[self testSymmetryWithPredicate:predicate
mustRoundTrip:NO];
}
{
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"CAST(a, 'NSString') <=[cd] 'b'"];
NSArray *jsonExpression = @[@"<=", @[@"to-string", @[@"get", @"a"]], @"b", @[@"collator", @{@"case-sensitive": @NO, @"diacritic-sensitive": @NO}]];
XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, jsonExpression);
[self testSymmetryWithPredicate:predicate
mustRoundTrip:NO];
}
{
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"CAST(a, 'NSString') >[cd] 'b'"];
NSArray *jsonExpression = @[@">", @[@"to-string", @[@"get", @"a"]], @"b", @[@"collator", @{@"case-sensitive": @NO, @"diacritic-sensitive": @NO}]];
XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, jsonExpression);
[self testSymmetryWithPredicate:predicate
mustRoundTrip:NO];
}
{
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"CAST(a, 'NSString') >=[cd] 'b'"];
NSArray *jsonExpression = @[@">=", @[@"to-string", @[@"get", @"a"]], @"b", @[@"collator", @{@"case-sensitive": @NO, @"diacritic-sensitive": @NO}]];
XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, jsonExpression);
[self testSymmetryWithPredicate:predicate
mustRoundTrip:NO];
}
{
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"TRUE = MGL_FUNCTION('==', a, 'b', MGL_FUNCTION('collator', %@))", @{
@"case-sensitive": @NO,
@"diacritic-sensitive": @NO,
@"locale": @"tlh",
}];
NSArray *jsonExpression = @[@"==", @[@"get", @"a"], @"b",
@[@"collator",
@{@"case-sensitive": @NO,
@"diacritic-sensitive": @NO,
@"locale": @"tlh"}]];
XCTAssertEqualObjects([predicate.mgl_jsonExpressionObject lastObject], jsonExpression);
}
}
- (void)testCompoundPredicates {
{
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"a == 'b' AND c == 'd'"];

View File

@@ -12,6 +12,8 @@ Mapbox welcomes participation and contributions from everyone. Please read [CONT
* Added an `MGLRasterStyleLayer.rasterResamplingMode` property for configuring how raster style layers are overscaled. ([#12176](https://github.com/mapbox/mapbox-gl-native/pull/12176))
* `-[MGLStyle localizeLabelsIntoLocale:]` and `-[NSExpression mgl_expressionLocalizedIntoLocale:]` can automatically localize labels into Japanese or Korean based on the system’s language settings. ([#12286](https://github.com/mapbox/mapbox-gl-native/pull/12286))
* The `c` and `d` options are supported within comparison predicates for case and diacritic insensitivity, respectively. ([#12329](https://github.com/mapbox/mapbox-gl-native/pull/12329))
* Added the `collator` and `resolved-locale` expression operators to more precisely compare strings in style JSON. A subset of this functionality is available through predicate options when creating an `NSPredicate`. ([#11869](https://github.com/mapbox/mapbox-gl-native/pull/11869))
* Fixed a crash when trying to parse expressions containing legacy filters. ([#12263](https://github.com/mapbox/mapbox-gl-native/pull/12263))
### Networking and storage

View File

@@ -322,6 +322,7 @@ In style specification | Method, function, or predicate type | Format string syn
-----------------------|-------------------------------------|---------------------
`array` | |
`boolean` | |
`collator` | `NSComparisonPredicateOptions` | `'Québec' =[cd] 'QUEBEC'`
`literal` | `+[NSExpression expressionForConstantValue:]` | `%@` representing `NSArray` or `NSDictionary`
`number` | |
`string` | |

View File

@@ -6,6 +6,8 @@
* Added an `MGLRasterStyleLayer.rasterResamplingMode` property for configuring how raster style layers are overscaled. ([#12176](https://github.com/mapbox/mapbox-gl-native/pull/12176))
* `-[MGLStyle localizeLabelsIntoLocale:]` and `-[NSExpression mgl_expressionLocalizedIntoLocale:]` can automatically localize labels into Japanese or Korean based on the system’s language settings. ([#12286](https://github.com/mapbox/mapbox-gl-native/pull/12286))
* The `c` and `d` options are supported within comparison predicates for case and diacritic insensitivity, respectively. ([#12329](https://github.com/mapbox/mapbox-gl-native/pull/12329))
* Added the `collator` and `resolved-locale` expression operators to more precisely compare strings in style JSON. A subset of this functionality is available through predicate options when creating an `NSPredicate`. ([#11869](https://github.com/mapbox/mapbox-gl-native/pull/11869))
* Fixed a crash in `-[MGLStyle localizeLabelsIntoLocale:]` on macOS 10.11. ([#12123](https://github.com/mapbox/mapbox-gl-native/pull/12123))
* Fixed a crash when trying to parse expressions containing legacy filters. ([#12263](https://github.com/mapbox/mapbox-gl-native/pull/12263))

View File

@@ -315,6 +315,7 @@ In style specification | Method, function, or predicate type | Format string syn
-----------------------|-------------------------------------|---------------------
`array` | |
`boolean` | |
`collator` | `NSComparisonPredicateOptions` | `'Québec' =[cd] 'QUEBEC'`
`literal` | `+[NSExpression expressionForConstantValue:]` | `%@` representing `NSArray` or `NSDictionary`
`number` | |
`string` | |