XADMaster/XADRegex.m
2018-03-21 14:24:50 +02:00

297 lines
8.6 KiB
Objective-C

/*
* XADRegex.m
*
* Copyright (c) 2017-present, MacPaw Inc. All rights reserved.
*
* 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 "XADRegex.h"
static BOOL IsRegexSpecialCharacter(unichar c)
{
return c=='^'||c=='.'||c=='['||c=='$'||c=='('||c==')'||
c=='|'||c=='*'||c=='+'||c=='?'||c=='{'||c=='\\';
}
@implementation XADRegex
+(XADRegex *)regexWithPattern:(NSString *)pattern options:(int)options
{ return [[[XADRegex alloc] initWithPattern:pattern options:options] autorelease]; }
+(XADRegex *)regexWithPattern:(NSString *)pattern
{ return [[[XADRegex alloc] initWithPattern:pattern options:0] autorelease]; }
+(NSString *)null
{
static NSString *nullstring=nil;
if(!nullstring) nullstring=[[NSMutableString alloc] initWithString:@""];
return nullstring;
}
+(NSString *)patternForLiteralString:(NSString *)string
{
long len=[string length];
NSMutableString *escaped=[NSMutableString stringWithCapacity:len];
for(long i=0;i<len;i++)
{
unichar c=[string characterAtIndex:i];
if(IsRegexSpecialCharacter(c)) [escaped appendFormat:@"\\%C",c];
else [escaped appendFormat:@"%C",c];
}
return [NSString stringWithString:escaped];
}
+(NSString *)patternForGlob:(NSString *)glob
{
long len=[glob length];
NSMutableString *pattern=[NSMutableString stringWithCapacity:len+2];
[pattern appendString:@"^"];
for(long i=0;i<len;i++)
{
unichar c=[glob characterAtIndex:i];
if(c=='\\')
{
if(i==len-1) [pattern appendString:@"\\"];
else
{
i++;
unichar c=[glob characterAtIndex:i];
if(IsRegexSpecialCharacter(c)) [pattern appendFormat:@"\\%C",c];
else [pattern appendFormat:@"%C",c];
}
}
else if(c=='*')
{
[pattern appendString:@".*"];
}
else if(c=='?')
{
[pattern appendString:@"."];
}
else if(c=='[')
{
[pattern appendString:@"["];
}
else if(c==']')
{
[pattern appendString:@"]"];
}
else if(IsRegexSpecialCharacter(c))
{
[pattern appendFormat:@"\\%C",c];
}
else
{
[pattern appendFormat:@"%C",c];
}
}
[pattern appendString:@"$"];
return [NSString stringWithString:pattern];
}
-(id)initWithPattern:(NSString *)pattern options:(int)options
{
if((self=[super init]))
{
patternstring=[pattern retain];
currdata=nil;
matches=NULL;
int err=regcomp(&preg,[pattern UTF8String],options|REG_EXTENDED);
if(err)
{
[self autorelease];
char errbuf[256];
regerror(err,&preg,errbuf,sizeof(errbuf));
[NSException raise:@"XADRegexException" format:@"Could not compile regex \"%@\": %s",pattern,errbuf];
}
matches=calloc(sizeof(regmatch_t),preg.re_nsub+1);
if(!matches)
{
[self autorelease];
[NSException raise:NSMallocException format:@"Out of memory when creating regex \"%@\"",pattern];
}
}
return self;
}
-(void)dealloc
{
[patternstring release];
regfree(&preg);
free(matches);
[currdata release];
[super dealloc];
}
-(void)beginMatchingString:(NSString *)string { [self beginMatchingData:[string dataUsingEncoding:NSUTF8StringEncoding]]; }
-(void)beginMatchingData:(NSData *)data { [self beginMatchingData:data range:NSMakeRange(0,[data length])]; }
-(void)beginMatchingData:(NSData *)data range:(NSRange)range
{
matchrange=range;
if(data==currdata) return;
[currdata release];
currdata=[data retain];
}
-(void)finishMatching { [currdata release]; currdata=nil; }
-(BOOL)matchNext
{
matches[0].rm_so=matchrange.location;
matches[0].rm_eo=matchrange.location+matchrange.length;
if(regexec(&preg,[currdata bytes],preg.re_nsub+1,matches,REG_STARTEND)==0)
{
matchrange.length-=matches[0].rm_eo-matchrange.location;
matchrange.location=(long)matches[0].rm_eo;
return YES;
}
[self finishMatching];
return NO;
}
-(NSString *)stringForMatch:(int)n
{
if(n>preg.re_nsub||n<0) [NSException raise:NSRangeException format:@"Index %d out of range for regex \"%@\"",n,self];
if(matches[n].rm_so==-1&&matches[n].rm_eo==-1) return nil;
return [[[NSString alloc] initWithBytes:[currdata bytes]+matches[n].rm_so
length:(long)(matches[n].rm_eo-matches[n].rm_so) encoding:NSUTF8StringEncoding] autorelease];
}
-(NSArray *)allMatches
{
NSMutableArray *array=[NSMutableArray arrayWithCapacity:preg.re_nsub+1];
for(int i=0;i<=preg.re_nsub;i++)
{
NSString *str=[self stringForMatch:i];
if(str) [array addObject:str];
else [array addObject:[XADRegex null]];
}
return [NSArray arrayWithArray:array];
}
-(BOOL)matchesString:(NSString *)string
{
[self beginMatchingString:string];
BOOL res=[self matchNext];
[self finishMatching];
return res;
}
-(NSString *)matchedSubstringOfString:(NSString *)string
{
[self beginMatchingString:string];
NSString *res=nil;
if([self matchNext]) res=[self stringForMatch:0];
[self finishMatching];
return res;
}
-(NSArray *)capturedSubstringsOfString:(NSString *)string
{
[self beginMatchingString:string];
NSArray *res=nil;
if([self matchNext]) res=[self allMatches];
[self finishMatching];
return res;
}
-(NSArray *)allMatchedSubstringsOfString:(NSString *)string
{
[self beginMatchingString:string];
NSMutableArray *array=[NSMutableArray array];
while([self matchNext]) [array addObject:[self stringForMatch:0]];
[self finishMatching];
return [NSArray arrayWithArray:array];
}
-(NSArray *)allCapturedSubstringsOfString:(NSString *)string
{
[self beginMatchingString:string];
NSMutableArray *array=[NSMutableArray array];
while([self matchNext]) [array addObject:[self allMatches]];
[self finishMatching];
return [NSArray arrayWithArray:array];
}
-(NSArray *)componentsOfSeparatedString:(NSString *)string
{
[self beginMatchingString:string];
NSMutableArray *array=[NSMutableArray array];
regoff_t prevstart=0;
const char *bytes=[currdata bytes];
while([self matchNext])
{
[array addObject:[[[NSString alloc] initWithBytes:bytes+prevstart length:(long)(matches[0].rm_so-prevstart)
encoding:NSUTF8StringEncoding] autorelease]];
prevstart=matches[0].rm_eo;
}
[array addObject:[[[NSString alloc] initWithBytes:bytes+prevstart length:(long)([currdata length]-prevstart)
encoding:NSUTF8StringEncoding] autorelease]];
[self finishMatching];
return [NSArray arrayWithArray:array];
}
-(NSString *)pattern { return patternstring; }
-(NSString *)description { return patternstring; }
@end
@implementation NSString (XADRegex)
-(BOOL)matchedByPattern:(NSString *)pattern { return [self matchedByPattern:pattern options:0]; }
-(BOOL)matchedByPattern:(NSString *)pattern options:(int)options
{ return [[XADRegex regexWithPattern:pattern options:options] matchesString:self]; }
-(NSString *)substringMatchedByPattern:(NSString *)pattern { return [self substringMatchedByPattern:pattern options:0]; }
-(NSString *)substringMatchedByPattern:(NSString *)pattern options:(int)options
{ return [[XADRegex regexWithPattern:pattern options:options] matchedSubstringOfString:self]; }
-(NSArray *)substringsCapturedByPattern:(NSString *)pattern { return [self substringsCapturedByPattern:pattern options:0]; }
-(NSArray *)substringsCapturedByPattern:(NSString *)pattern options:(int)options
{ return [[XADRegex regexWithPattern:pattern options:options] capturedSubstringsOfString:self]; }
-(NSArray *)allSubstringsMatchedByPattern:(NSString *)pattern { return [self allSubstringsMatchedByPattern:pattern options:0]; }
-(NSArray *)allSubstringsMatchedByPattern:(NSString *)pattern options:(int)options
{ return [[XADRegex regexWithPattern:pattern options:options] allMatchedSubstringsOfString:self]; }
-(NSArray *)allSubstringsCapturedByPattern:(NSString *)pattern { return [self allSubstringsCapturedByPattern:pattern options:0]; }
-(NSArray *)allSubstringsCapturedByPattern:(NSString *)pattern options:(int)options
{ return [[XADRegex regexWithPattern:pattern options:options] allCapturedSubstringsOfString:self]; }
-(NSArray *)componentsSeparatedByPattern:(NSString *)pattern { return [self componentsSeparatedByPattern:pattern options:0]; }
-(NSArray *)componentsSeparatedByPattern:(NSString *)pattern options:(int)options
{ return [[XADRegex regexWithPattern:pattern options:options] componentsOfSeparatedString:self]; }
-(NSString *)escapedPattern { return [XADRegex patternForLiteralString:self]; }
@end