Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions DeepLinkSDK.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
/* Begin PBXBuildFile section */
1252340A196B4771BE27D5FD /* libPods-ReceiverDemo.a in Frameworks */ = {isa = PBXBuildFile; fileRef = CFE63F7FEA51182807D98A78 /* libPods-ReceiverDemo.a */; };
2F4988DE1AE71ABC0069EF2B /* DPLRouteHandlerIntegrationTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 2F4988DD1AE71ABC0069EF2B /* DPLRouteHandlerIntegrationTest.m */; };
4D4F412B1B02A96400B710DB /* DPLRegularExpressionSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = 4D4F412A1B02A96400B710DB /* DPLRegularExpressionSpec.m */; };
6003F58E195388D20070C39A /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6003F58D195388D20070C39A /* Foundation.framework */; };
6003F590195388D20070C39A /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6003F58F195388D20070C39A /* CoreGraphics.framework */; };
6003F592195388D20070C39A /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6003F591195388D20070C39A /* UIKit.framework */; };
Expand Down Expand Up @@ -65,6 +66,11 @@
19367BAF0FEDE5B798128F3D /* libPods-Tests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-Tests.a"; sourceTree = BUILT_PRODUCTS_DIR; };
2AE3E05821FBC0C05F248E61 /* Pods-Tests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Tests.debug.xcconfig"; path = "Pods/Target Support Files/Pods-Tests/Pods-Tests.debug.xcconfig"; sourceTree = "<group>"; };
2F4988DD1AE71ABC0069EF2B /* DPLRouteHandlerIntegrationTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = DPLRouteHandlerIntegrationTest.m; path = IntegrationTests/DPLRouteHandlerIntegrationTest.m; sourceTree = "<group>"; };
4D4F41231B0298FF00B710DB /* DPLRegularExpression.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DPLRegularExpression.h; sourceTree = "<group>"; };
4D4F41241B0298FF00B710DB /* DPLRegularExpression.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DPLRegularExpression.m; sourceTree = "<group>"; };
4D4F41251B029D9E00B710DB /* DPLMatchResult.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DPLMatchResult.h; sourceTree = "<group>"; };
4D4F41261B029D9E00B710DB /* DPLMatchResult.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DPLMatchResult.m; sourceTree = "<group>"; };
4D4F412A1B02A96400B710DB /* DPLRegularExpressionSpec.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DPLRegularExpressionSpec.m; sourceTree = "<group>"; };
57D5F02E049D7887B4F4ACDF /* Pods-ReceiverDemo.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ReceiverDemo.debug.xcconfig"; path = "Pods/Target Support Files/Pods-ReceiverDemo/Pods-ReceiverDemo.debug.xcconfig"; sourceTree = "<group>"; };
6003F58A195388D20070C39A /* ReceiverDemo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = ReceiverDemo.app; sourceTree = BUILT_PRODUCTS_DIR; };
6003F58D195388D20070C39A /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; };
Expand Down Expand Up @@ -220,6 +226,25 @@
name = Pods;
sourceTree = "<group>";
};
4D4F41221B0298AA00B710DB /* Regex */ = {
isa = PBXGroup;
children = (
4D4F41231B0298FF00B710DB /* DPLRegularExpression.h */,
4D4F41241B0298FF00B710DB /* DPLRegularExpression.m */,
4D4F41251B029D9E00B710DB /* DPLMatchResult.h */,
4D4F41261B029D9E00B710DB /* DPLMatchResult.m */,
);
path = Regex;
sourceTree = "<group>";
};
4D4F41291B02A95800B710DB /* Regex */ = {
isa = PBXGroup;
children = (
4D4F412A1B02A96400B710DB /* DPLRegularExpressionSpec.m */,
);
path = Regex;
sourceTree = "<group>";
};
6003F581195388D10070C39A = {
isa = PBXGroup;
children = (
Expand Down Expand Up @@ -405,6 +430,7 @@
DE99EF6A1A3B6CDD00CE3449 /* Protocols */,
DE3E61071A3B4485008D6DFC /* Categories */,
DEB4EDBB1A49CEA400F31D14 /* Errors */,
4D4F41221B0298AA00B710DB /* Regex */,
DE16E91F1A42882F00077E18 /* Router */,
DEAC406F1A5DA7B8004A9095 /* RouteHandler */,
DE16E9331A4289D500077E18 /* RouteMatcher */,
Expand Down Expand Up @@ -481,6 +507,7 @@
children = (
2F4988DC1AE71A930069EF2B /* IntegrationTests */,
DEEBD4A61AAB7928000BCA84 /* Fixtures */,
4D4F41291B02A95800B710DB /* Regex */,
DE16E9221A42883B00077E18 /* Router */,
DE16E9361A4289DF00077E18 /* RouteMatcher */,
DE058E0E1A3B485500147C04 /* DeepLink */,
Expand Down Expand Up @@ -806,6 +833,7 @@
files = (
DE16E9281A42883B00077E18 /* DPLDeepLinkRouterSpec.m in Sources */,
DE16E9381A4289DF00077E18 /* DPLRouteMatcherSpec.m in Sources */,
4D4F412B1B02A96400B710DB /* DPLRegularExpressionSpec.m in Sources */,
DECB32531A881CA10071C76E /* NSObject_DPLJSONObjectSpec.m in Sources */,
DEEBD4A91AAB7946000BCA84 /* DPLSerializableObject.m in Sources */,
DE058E101A3B485500147C04 /* DPLDeepLinkSpec.m in Sources */,
Expand Down
8 changes: 8 additions & 0 deletions DeepLinkSDK/Regex/DPLMatchResult.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
#import <Foundation/Foundation.h>

@interface DPLMatchResult : NSObject

@property (nonatomic, assign, getter=isMatch) BOOL match;
@property (nonatomic, strong) NSDictionary *namedProperties;

@end
5 changes: 5 additions & 0 deletions DeepLinkSDK/Regex/DPLMatchResult.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
#import "DPLMatchResult.h"

@implementation DPLMatchResult

@end
12 changes: 12 additions & 0 deletions DeepLinkSDK/Regex/DPLRegularExpression.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
#import <Foundation/Foundation.h>
#import "DPLMatchResult.h"

@interface DPLRegularExpression : NSRegularExpression

@property (nonatomic, strong) NSArray *groupNames;

+ (instancetype)regularExpressionWithPattern:(NSString *)pattern;

- (DPLMatchResult *)matchResultForString:(NSString *)str;

@end
135 changes: 135 additions & 0 deletions DeepLinkSDK/Regex/DPLRegularExpression.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
#import "DPLRegularExpression.h"

static NSString * const DPLNamedGroupComponentPattern = @":[a-zA-Z0-9-_][^/]+";
static NSString * const DPLRouteParameterPattern = @":[a-zA-Z0-9-_]+";
static NSString * const DPLURLParameterPattern = @"([^/]+)";

@implementation DPLRegularExpression

+ (instancetype)regularExpressionWithPattern:(NSString *)pattern {
return [[self alloc] initWithPattern:pattern options:0 error:nil];
}


- (instancetype)initWithPattern:(NSString *)pattern
options:(NSRegularExpressionOptions)options
error:(NSError *__autoreleasing *)error {

NSString *cleanedPattern = [[self class] stringByRemovingNamedGroupsFromString:pattern];

self = [super initWithPattern:cleanedPattern options:options error:error];
if (self) {
self.groupNames = [[self class] namedGroupsForString:pattern];
}
return self;
}


- (DPLMatchResult *)matchResultForString:(NSString *)str {
NSArray *matches = [self matchesInString:str options:0 range:NSMakeRange(0, str.length)];
DPLMatchResult *matchResult = [[DPLMatchResult alloc] init];

if (!matches.count) {
return matchResult;
}

matchResult.match = YES;

// Set route parameters in the routeParameters dictionary
NSMutableDictionary *routeParameters = [NSMutableDictionary dictionary];
for (NSTextCheckingResult *result in matches) {
// Begin at 1 as first range is the whole match
for (NSInteger i = 1; i < result.numberOfRanges && i <= self.groupNames.count; i++) {
NSString *parameterName = self.groupNames[i - 1];
NSString *parameterValue = [str substringWithRange:[result rangeAtIndex:i]];
routeParameters[parameterName] = parameterValue;
}
}

matchResult.namedProperties = routeParameters;
return matchResult;
}


#pragma mark - Named Group Helpers

+ (NSArray *)namedGroupTokensForString:(NSString *)str {
NSRegularExpression *componentRegex = [NSRegularExpression regularExpressionWithPattern:DPLNamedGroupComponentPattern
options:0
error:nil];
NSArray *matches = [componentRegex matchesInString:str
options:0
range:NSMakeRange(0, str.length)];

NSMutableArray *namedGroupTokens = [NSMutableArray array];
for (NSTextCheckingResult *result in matches) {
NSString *namedGroupToken = [str substringWithRange:result.range];
[namedGroupTokens addObject:namedGroupToken];
}
return [NSArray arrayWithArray:namedGroupTokens];
}


+ (NSString *)stringByRemovingNamedGroupsFromString:(NSString *)str {
NSString *modifiedStr = [str copy];

NSArray *namedGroupExpressions = [self namedGroupTokensForString:str];
NSRegularExpression *parameterRegex = [NSRegularExpression regularExpressionWithPattern:DPLRouteParameterPattern
options:0
error:nil];
// For each of the named group expressions (including name & regex)
for (NSString *namedExpression in namedGroupExpressions) {
NSString *replacementExpression = [namedExpression copy];
NSTextCheckingResult *foundGroupName = [[parameterRegex matchesInString:namedExpression
options:0
range:NSMakeRange(0, namedExpression.length)] firstObject];
// If it's a named group, remove the name
if (foundGroupName) {
NSString *stringToReplace = [namedExpression substringWithRange:foundGroupName.range];
replacementExpression = [replacementExpression stringByReplacingOccurrencesOfString:stringToReplace
withString:@""];
}

// If it was a named group, without regex constraining it, put in default regex
if (replacementExpression.length == 0) {
replacementExpression = DPLURLParameterPattern;
}

modifiedStr = [modifiedStr stringByReplacingOccurrencesOfString:namedExpression
withString:replacementExpression];
}

if (modifiedStr.length && !([modifiedStr characterAtIndex:0] == '/')) {
modifiedStr = [@"^" stringByAppendingString:modifiedStr];
}
modifiedStr = [modifiedStr stringByAppendingString:@"$"];

return modifiedStr;
}


+ (NSArray *)namedGroupsForString:(NSString *)str {
NSMutableArray *groupNames = [NSMutableArray array];

NSArray *namedGroupExpressions = [self namedGroupTokensForString:str];
NSRegularExpression *parameterRegex = [NSRegularExpression regularExpressionWithPattern:DPLRouteParameterPattern
options:0
error:nil];

for (NSString *namedExpression in namedGroupExpressions) {
NSArray *componentMatches = [parameterRegex matchesInString:namedExpression
options:0
range:NSMakeRange(0, namedExpression.length)];
NSTextCheckingResult *foundGroupName = [componentMatches firstObject];
if (foundGroupName) {
NSString *stringToReplace = [namedExpression substringWithRange:foundGroupName.range];
NSString *variableName = [stringToReplace stringByReplacingOccurrencesOfString:@":"
withString:@""];

[groupNames addObject:variableName];
}
}
return [NSArray arrayWithArray:groupNames];
}

@end
62 changes: 7 additions & 55 deletions DeepLinkSDK/RouteMatcher/DPLRouteMatcher.m
Original file line number Diff line number Diff line change
@@ -1,15 +1,11 @@
#import "DPLRouteMatcher.h"
#import "DPLDeepLink_Private.h"
#import "NSString+DPLTrim.h"

static NSString * const DPLRouteParameterPattern = @":[a-zA-Z0-9-_]+";
static NSString * const DPLURLParameterPattern = @"([^/]+)";
#import "DPLRegularExpression.h"

@interface DPLRouteMatcher ()

@property (nonatomic, copy) NSString *route;
@property (nonatomic, strong) NSRegularExpression *regex;
@property (nonatomic, strong) NSMutableArray *routeParamaterNames;
@property (nonatomic, strong) DPLRegularExpression *regexMatcher;

@end

Expand All @@ -27,70 +23,26 @@ - (instancetype)initWithRoute:(NSString *)route {

self = [super init];
if (self) {
_route = route;
_regexMatcher = [DPLRegularExpression regularExpressionWithPattern:route];
}

return self;
}


- (NSRegularExpression *)regex {
if (!_regex) {
_routeParamaterNames = [NSMutableArray array];
NSRegularExpression *parameterRegex = [NSRegularExpression regularExpressionWithPattern:DPLRouteParameterPattern
options:0
error:nil];

__block NSString *modifiedRoute = [self.route copy];
NSArray *matches = [parameterRegex matchesInString:self.route
options:0
range:NSMakeRange(0, self.route.length)];

for (NSTextCheckingResult *result in matches) {

NSString *stringToReplace = [self.route substringWithRange:result.range];
NSString *variableName = [stringToReplace stringByReplacingOccurrencesOfString:@":"
withString:@""];
[self.routeParamaterNames addObject:variableName];

modifiedRoute = [modifiedRoute stringByReplacingOccurrencesOfString:stringToReplace
withString:DPLURLParameterPattern];
}

modifiedRoute = [modifiedRoute stringByAppendingString:@"$"];
_regex = [NSRegularExpression regularExpressionWithPattern:modifiedRoute
options:0
error:nil];
}

return _regex;
}


- (DPLDeepLink *)deepLinkWithURL:(NSURL *)url {

DPLDeepLink *deepLink = [[DPLDeepLink alloc] initWithURL:url];
NSString *deepLinkString = [NSString stringWithFormat:@"%@%@",
deepLink.URL.host, deepLink.URL.path];
NSArray *matches = [self.regex matchesInString:deepLinkString
options:0
range:NSMakeRange(0, deepLinkString.length)];

if (!matches.count) {

DPLMatchResult *matchResult = [self.regexMatcher matchResultForString:deepLinkString];
if (!matchResult.isMatch) {
return nil;
}

// Set route parameters in the routeParameters dictionary
NSMutableDictionary *routeParameters = [NSMutableDictionary dictionary];
for (NSTextCheckingResult *result in matches) {
// Begin at 1 as first range is the whole match
for (int i = 1; i < result.numberOfRanges && i <= self.routeParamaterNames.count; i++) {
NSString *parameterName = self.routeParamaterNames[i - 1];
NSString *parameterValue = [deepLinkString substringWithRange:[result rangeAtIndex:i]];
routeParameters[parameterName] = parameterValue;
}
}
deepLink.routeParameters = routeParameters;
deepLink.routeParameters = matchResult.namedProperties;

return deepLink;
}
Expand Down
2 changes: 1 addition & 1 deletion Rakefile
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
desc "Runs the specs"
task :spec do
sh "xcodebuild test -workspace 'DeepLinkSDK.xcworkspace' -scheme 'DeepLinkSDK' -destination 'platform=iOS Simulator,name=iPhone 6,OS=8.1' | xcpretty -c"
sh "xcodebuild test -workspace 'DeepLinkSDK.xcworkspace' -scheme 'ReceiverDemo' -destination 'platform=iOS Simulator,name=iPhone 6,OS=8.1' | xcpretty -c"
end

desc "Synchronizes Xcode project folder with Xcode groups"
Expand Down
3 changes: 1 addition & 2 deletions Tests/IntegrationTests/DPLRouteHandlerIntegrationTest.m
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,7 @@ @interface DPLRouteHandlerIntegrationTest : XCTestCase

@implementation DPLRouteHandlerIntegrationTest

- (void)testClassBasedRouteHandlerFlow
{
- (void)testClassBasedRouteHandlerFlow {
[[UIApplication sharedApplication] openURL:[NSURL URLWithString:@"dpl://dpl.com/product/93598?"]];
[tester waitForViewWithAccessibilityLabel:@"Shiner Oktoberfest"];
[tester waitForViewWithAccessibilityLabel:@"8.99"];
Expand Down
47 changes: 47 additions & 0 deletions Tests/Regex/DPLRegularExpressionSpec.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
#import "Specta.h"
#import "DPLRegularExpression.h"

SpecBegin(DPLRegularExpression)

describe(@"Matching named components", ^{

it(@"should match named components without regex", ^{
DPLRegularExpression *expression = [DPLRegularExpression regularExpressionWithPattern:@"/hello/:do/:this/and/:that"];

DPLMatchResult *matchResult = [expression matchResultForString:@"/hello/dovalue/thisvalue/and/thatvalue"];
expect(matchResult.match).to.beTruthy();
expect(matchResult.namedProperties).to.equal(@{ @"do": @"dovalue",
@"this": @"thisvalue",
@"that": @"thatvalue" });
});

it(@"should match named components with regex", ^{
DPLRegularExpression *expression = [DPLRegularExpression regularExpressionWithPattern:@"/hello/:do([a-zA-Z]+)/:this([a-zA-Z]+)/and/:that([a-zA-Z]+)"];

DPLMatchResult *matchResult = [expression matchResultForString:@"/hello/dovalue/thisvalue/and/thatvalue"];
expect(matchResult.match).to.beTruthy();
expect(matchResult.namedProperties).to.equal(@{ @"do": @"dovalue",
@"this": @"thisvalue",
@"that": @"thatvalue" });
});

it(@"should match a mixture of with and without regex", ^{
DPLRegularExpression *expression = [DPLRegularExpression regularExpressionWithPattern:@"/hello/:do/:this([a-zA-Z]+)/and/:that([a-zA-Z]+)"];

DPLMatchResult *matchResult = [expression matchResultForString:@"/hello/dovalue/thisvalue/and/thatvalue"];
expect(matchResult.match).to.beTruthy();
expect(matchResult.namedProperties).to.equal(@{ @"do": @"dovalue",
@"this": @"thisvalue",
@"that": @"thatvalue" });
});

it(@"should NOT match named components with regex that does not match", ^{
DPLRegularExpression *expression = [DPLRegularExpression regularExpressionWithPattern:@"/hello/:do([a-zA-Z]+)/:this([0-9]+)/and/:that([a-zA-Z]+)"];

DPLMatchResult *matchResult = [expression matchResultForString:@"/hello/dovalue/thisvalue/and/thatvalue"];
expect(matchResult.match).to.beFalsy();
});

});

SpecEnd
Loading