[ios, macos] Convert predicate options to collators
This commit is contained in:
committed by
Minh Nguyễn
parent
d6a454ba8e
commit
f11ab91fc4
@@ -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` | |
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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'"];
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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` | |
|
||||
|
||||
@@ -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))
|
||||
|
||||
|
||||
@@ -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` | |
|
||||
|
||||
Reference in New Issue
Block a user