From f11ab91fc448aca1155b42a53aaa77cfce62f412 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Minh=20Nguye=CC=82=CC=83n?= Date: Thu, 5 Jul 2018 15:08:24 -0700 Subject: [ios, macos] Convert predicate options to collators --- .../darwin/docs/guides/For Style Authors.md.ejs | 1 + .../docs/guides/Predicates and Expressions.md | 14 ++++- .../src/NSComparisonPredicate+MGLAdditions.mm | 14 ++++- platform/darwin/src/NSExpression+MGLAdditions.mm | 9 +++ platform/darwin/src/NSPredicate+MGLAdditions.mm | 60 +++++++++++------- platform/darwin/test/MGLPredicateTests.mm | 73 ++++++++++++++++++++++ platform/ios/CHANGELOG.md | 2 + platform/ios/docs/guides/For Style Authors.md | 1 + platform/macos/CHANGELOG.md | 2 + platform/macos/docs/guides/For Style Authors.md | 1 + 10 files changed, 149 insertions(+), 28 deletions(-) diff --git a/platform/darwin/docs/guides/For Style Authors.md.ejs b/platform/darwin/docs/guides/For Style Authors.md.ejs index 60177e57c2..b8112d6fec 100644 --- a/platform/darwin/docs/guides/For Style Authors.md.ejs +++ b/platform/darwin/docs/guides/For Style Authors.md.ejs @@ -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` | | diff --git a/platform/darwin/docs/guides/Predicates and Expressions.md b/platform/darwin/docs/guides/Predicates and Expressions.md index e0d4755d4a..4ef9ad6707 100644 --- a/platform/darwin/docs/guides/Predicates and Expressions.md +++ b/platform/darwin/docs/guides/Predicates and Expressions.md @@ -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 diff --git a/platform/darwin/src/NSComparisonPredicate+MGLAdditions.mm b/platform/darwin/src/NSComparisonPredicate+MGLAdditions.mm index 15aa71419d..af9216f9ce 100644 --- a/platform/darwin/src/NSComparisonPredicate+MGLAdditions.mm +++ b/platform/darwin/src/NSComparisonPredicate+MGLAdditions.mm @@ -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 diff --git a/platform/darwin/src/NSExpression+MGLAdditions.mm b/platform/darwin/src/NSExpression+MGLAdditions.mm index 653e3d67e6..d03d7dbaec 100644 --- a/platform/darwin/src/NSExpression+MGLAdditions.mm +++ b/platform/darwin/src/NSExpression+MGLAdditions.mm @@ -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; diff --git a/platform/darwin/src/NSPredicate+MGLAdditions.mm b/platform/darwin/src/NSPredicate+MGLAdditions.mm index 4b9a4177cb..d01b2c8f83 100644 --- a/platform/darwin/src/NSPredicate+MGLAdditions.mm +++ b/platform/darwin/src/NSPredicate+MGLAdditions.mm @@ -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:@"=="]) { - 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:@">="]) { + 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]; + 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) { diff --git a/platform/darwin/test/MGLPredicateTests.mm b/platform/darwin/test/MGLPredicateTests.mm index 41c2d56868..ed92c920f6 100644 --- a/platform/darwin/test/MGLPredicateTests.mm +++ b/platform/darwin/test/MGLPredicateTests.mm @@ -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'"]; diff --git a/platform/ios/CHANGELOG.md b/platform/ios/CHANGELOG.md index 0f6aba3cfd..115a608918 100644 --- a/platform/ios/CHANGELOG.md +++ b/platform/ios/CHANGELOG.md @@ -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 diff --git a/platform/ios/docs/guides/For Style Authors.md b/platform/ios/docs/guides/For Style Authors.md index e3e2eca603..24a2f7d3a4 100644 --- a/platform/ios/docs/guides/For Style Authors.md +++ b/platform/ios/docs/guides/For Style Authors.md @@ -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` | | diff --git a/platform/macos/CHANGELOG.md b/platform/macos/CHANGELOG.md index 0dff689010..1d5eab97fb 100644 --- a/platform/macos/CHANGELOG.md +++ b/platform/macos/CHANGELOG.md @@ -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)) diff --git a/platform/macos/docs/guides/For Style Authors.md b/platform/macos/docs/guides/For Style Authors.md index e2d63a6506..40d1edef22 100644 --- a/platform/macos/docs/guides/For Style Authors.md +++ b/platform/macos/docs/guides/For Style Authors.md @@ -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` | | -- cgit v1.2.1