mirror of
https://github.com/gnustep/apps-projectcenter.git
synced 2025-02-19 10:00:48 +00:00
613 lines
15 KiB
Objective-C
613 lines
15 KiB
Objective-C
/*
|
|
TextPattern.m
|
|
|
|
Implementation of operations on text patterns for the
|
|
ProjectManager application.
|
|
|
|
Copyright (C) 2005 Saso Kiselkov
|
|
|
|
This program is free software; you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation; either version 2 of the License, or
|
|
(at your option) any later version.
|
|
|
|
This program 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 General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program; if not, write to the Free Software
|
|
Foundation, Inc., 31 Milk Street #960789 Boston, MA 02196 USA
|
|
*/
|
|
|
|
#import "TextPattern.h"
|
|
|
|
#import <Foundation/NSBundle.h>
|
|
#import <Foundation/NSScanner.h>
|
|
#import <Foundation/NSException.h>
|
|
|
|
static void
|
|
FreeTextPatternItem (TextPatternItem *item)
|
|
{
|
|
if (item->type == MultipleCharactersTextPatternItem)
|
|
{
|
|
free (item->data.multiChar.characters);
|
|
}
|
|
|
|
free (item);
|
|
}
|
|
|
|
static TextPatternItem *
|
|
ParseTextPatternItem (NSString *string, unsigned int *index)
|
|
{
|
|
unsigned int i = *index, n = [string length];
|
|
TextPatternItem * newItem;
|
|
unichar c;
|
|
|
|
newItem = (TextPatternItem *) calloc(1, sizeof (TextPatternItem));
|
|
|
|
c = [string characterAtIndex: i];
|
|
i++;
|
|
switch (c)
|
|
{
|
|
case '[':
|
|
{
|
|
unichar * buf = NULL;
|
|
unsigned int nChars = 0;
|
|
|
|
for (; i < n; i++)
|
|
{
|
|
unichar c = [string characterAtIndex: i];
|
|
|
|
// handle escapes
|
|
if (c == '\\')
|
|
{
|
|
if (i + 1 >= n)
|
|
{
|
|
NSLog(_(@"Text pattern item parse error in text "
|
|
@"pattern \"%@\" at index %i: unexpected end of "
|
|
@"pattern. Escape sequence expected."), string, i);
|
|
|
|
free (buf);
|
|
free (newItem);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
i++;
|
|
c = [string characterAtIndex: i];
|
|
}
|
|
else if (c == ']')
|
|
{
|
|
i++;
|
|
break;
|
|
}
|
|
|
|
nChars++;
|
|
buf = (unichar *) realloc(buf, sizeof (unichar) * nChars);
|
|
buf[nChars - 1] = c;
|
|
}
|
|
|
|
if (i == n)
|
|
{
|
|
NSLog(_(@"Text pattern item parse error in text pattern "
|
|
@"\"%@\" at index %i: unexpected end of character class."),
|
|
string, i);
|
|
|
|
free (buf);
|
|
free (newItem);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
newItem->type = MultipleCharactersTextPatternItem;
|
|
newItem->data.multiChar.nCharacters = nChars;
|
|
newItem->data.multiChar.characters = buf;
|
|
}
|
|
break;
|
|
case '.':
|
|
newItem->type = AnyCharacterTextPatternItem;
|
|
break;
|
|
case '<':
|
|
newItem->type = BeginningOfWordTextPatternItem;
|
|
break;
|
|
case '>':
|
|
newItem->type = EndingOfWordTextPatternItem;
|
|
break;
|
|
case '^':
|
|
newItem->type = BeginningOfLineTextPatternItem;
|
|
break;
|
|
case '$':
|
|
newItem->type = EndingOfLineTextPatternItem;
|
|
break;
|
|
case '\\':
|
|
if (i >= n)
|
|
{
|
|
NSLog(_(@"Text pattern item parse error in text pattern "
|
|
@"\"%@\" at index %i: unexpected end of pattern. Escape "
|
|
@"sequence expected."), string, i);
|
|
|
|
free (newItem);
|
|
return NULL;
|
|
}
|
|
c = [string characterAtIndex: i];
|
|
i++;
|
|
|
|
default:
|
|
newItem->type = SingleCharacterTextPatternItem;
|
|
newItem->data.singleChar = c;
|
|
break;
|
|
}
|
|
|
|
// is there trailing cardinality indication?
|
|
if (i < n)
|
|
{
|
|
c = [string characterAtIndex: i];
|
|
i++;
|
|
|
|
switch (c)
|
|
{
|
|
case '{':
|
|
{
|
|
NSScanner * scanner;
|
|
int value;
|
|
|
|
if (newItem->type != SingleCharacterTextPatternItem &&
|
|
newItem->type != MultipleCharactersTextPatternItem &&
|
|
newItem->type != AnyCharacterTextPatternItem)
|
|
{
|
|
NSLog(_(@"Text pattern item parse error in text pattern "
|
|
@"\"%@\" at index %i: no cardinality indication in "
|
|
@"'<', '>', '^' or '$' allowed."), string, i);
|
|
|
|
FreeTextPatternItem(newItem);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
scanner = [NSScanner scannerWithString: string];
|
|
|
|
[scanner setScanLocation: i];
|
|
if (![scanner scanInt: &value])
|
|
{
|
|
NSLog(_(@"Text pattern item parse error in text pattern "
|
|
@"\"%@\" at index %li: integer expected."), string,
|
|
(long int)[scanner scanLocation]);
|
|
|
|
FreeTextPatternItem(newItem);
|
|
|
|
return NULL;
|
|
}
|
|
newItem->minCount = newItem->maxCount = value;
|
|
i = [scanner scanLocation];
|
|
if (i + 1 >= n)
|
|
{
|
|
NSLog(_(@"Text pattern item parse error in text pattern "
|
|
@"\"%@\": unexpected end of pattern, '}' or ',' "
|
|
@"expected."), string);
|
|
|
|
FreeTextPatternItem(newItem);
|
|
|
|
return NULL;
|
|
}
|
|
c = [string characterAtIndex: i];
|
|
if (c == ',')
|
|
{
|
|
[scanner setScanLocation: i + 1];
|
|
if (![scanner scanInt: &value])
|
|
{
|
|
NSLog(_(@"Text pattern item parser error in text "
|
|
@"pattern \"%@\" at index %li: integer expected."),
|
|
string, (long int)[scanner scanLocation]);
|
|
|
|
FreeTextPatternItem(newItem);
|
|
|
|
return NULL;
|
|
}
|
|
newItem->maxCount = value;
|
|
i = [scanner scanLocation];
|
|
}
|
|
if (i >= n)
|
|
{
|
|
NSLog(_(@"Text pattern item parse error in text pattern "
|
|
@"\"%@\": unexpected end of pattern, '}' expected."),
|
|
string);
|
|
|
|
FreeTextPatternItem(newItem);
|
|
|
|
return NULL;
|
|
}
|
|
c = [string characterAtIndex: i];
|
|
i++;
|
|
if (c != '}')
|
|
{
|
|
NSLog(_(@"Text pattern item parse error in text pattern "
|
|
@"\"%@\" at index %i: '}' expected."), string, i);
|
|
|
|
FreeTextPatternItem(newItem);
|
|
|
|
return NULL;
|
|
}
|
|
}
|
|
break;
|
|
// no cardinality indication - the next character is part of
|
|
// the next text pattern
|
|
case '*':
|
|
newItem->minCount = 0;
|
|
newItem->maxCount = 0x7fffffff;
|
|
break;
|
|
case '?':
|
|
newItem->minCount = 0;
|
|
newItem->maxCount = 1;
|
|
break;
|
|
default:
|
|
i--;
|
|
newItem->minCount = newItem->maxCount = 1;
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
newItem->minCount = newItem->maxCount = 1;
|
|
}
|
|
|
|
*index = i;
|
|
|
|
return newItem;
|
|
}
|
|
|
|
#if 0
|
|
// not used
|
|
static void
|
|
DescribeTextPatternItem(TextPatternItem *item)
|
|
{
|
|
switch (item->type)
|
|
{
|
|
case SingleCharacterTextPatternItem:
|
|
NSLog(@" type: single char, value: '%c', min: %i, max: %i",
|
|
item->data.singleChar,
|
|
item->minCount,
|
|
item->maxCount);
|
|
break;
|
|
case MultipleCharactersTextPatternItem:
|
|
NSLog(@" type: multi char, value: '%@', min: %i, max: %i",
|
|
[NSString stringWithCharacters: item->data.multiChar.characters
|
|
length: item->data.multiChar.nCharacters],
|
|
item->minCount, item->maxCount);
|
|
break;
|
|
case BeginningOfWordTextPatternItem:
|
|
NSLog(@" type: beginning of word");
|
|
break;
|
|
case EndingOfWordTextPatternItem:
|
|
NSLog(@" type: ending of word");
|
|
break;
|
|
case AnyCharacterTextPatternItem:
|
|
NSLog(@" type: any character, min: %i, max: %i",
|
|
item->minCount, item->maxCount);
|
|
break;
|
|
case BeginningOfLineTextPatternItem:
|
|
NSLog(@" type: beginning of line");
|
|
break;
|
|
case EndingOfLineTextPatternItem:
|
|
NSLog(@" type: ending of line");
|
|
break;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
TextPattern *
|
|
CompileTextPattern (NSString *string)
|
|
{
|
|
TextPattern * pattern;
|
|
unsigned int i, n;
|
|
|
|
pattern = (TextPattern *) calloc(1, sizeof(TextPattern));
|
|
|
|
ASSIGN(pattern->string, string);
|
|
|
|
for (i = 0, n = [string length]; i < n;)
|
|
{
|
|
TextPatternItem * item;
|
|
|
|
item = ParseTextPatternItem(string, &i);
|
|
if (item == NULL)
|
|
{
|
|
FreeTextPattern (pattern);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
// enlarge the pattern buffer
|
|
pattern->nItems++;
|
|
pattern->items = (TextPatternItem **) realloc(pattern->items,
|
|
pattern->nItems * sizeof(TextPatternItem *));
|
|
pattern->items[pattern->nItems - 1] = item;
|
|
}
|
|
|
|
return pattern;
|
|
}
|
|
|
|
void
|
|
FreeTextPattern (TextPattern *pattern)
|
|
{
|
|
unsigned int i;
|
|
|
|
for (i = 0; i < pattern->nItems; i++)
|
|
{
|
|
FreeTextPatternItem(pattern->items[i]);
|
|
}
|
|
|
|
free(pattern->items);
|
|
|
|
TEST_RELEASE(pattern->string);
|
|
|
|
free(pattern);
|
|
}
|
|
|
|
static inline BOOL
|
|
IsMemberOfCharacterClass(unichar c, unichar *charClass, unsigned int n)
|
|
{
|
|
unsigned int i;
|
|
|
|
for (i = 0; i < n; i++)
|
|
{
|
|
if (charClass[i] == c)
|
|
{
|
|
return YES;
|
|
}
|
|
}
|
|
|
|
return NO;
|
|
}
|
|
|
|
/**
|
|
* Returns YES if the passed character argument is an alphanumeric
|
|
* character, and NO if it isn't.
|
|
*/
|
|
static inline BOOL
|
|
my_isalnum (unichar c)
|
|
{
|
|
if ((c >= 'a' && c <= 'z') ||
|
|
(c >= 'A' && c <= 'Z') ||
|
|
(c >= '0' && c <= '9'))
|
|
{
|
|
return YES;
|
|
}
|
|
else
|
|
{
|
|
return NO;
|
|
}
|
|
}
|
|
|
|
static inline BOOL
|
|
CheckTextPatternItemPresence(TextPatternItem *item,
|
|
unichar *string,
|
|
unsigned int stringLength,
|
|
unsigned int *offset)
|
|
{
|
|
switch (item->type)
|
|
{
|
|
case SingleCharacterTextPatternItem:
|
|
{
|
|
unsigned int i;
|
|
unsigned int n;
|
|
|
|
// read characters while they are equal to our letter
|
|
for (n = 0, i = *offset;
|
|
i < stringLength && n < item->maxCount;
|
|
i++, n++)
|
|
{
|
|
if (string[i] != item->data.singleChar)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (n >= item->minCount)
|
|
{
|
|
*offset = i;
|
|
return YES;
|
|
}
|
|
else
|
|
{
|
|
return NO;
|
|
}
|
|
}
|
|
break;
|
|
case MultipleCharactersTextPatternItem:
|
|
{
|
|
unsigned int i;
|
|
unsigned int n;
|
|
|
|
for (n = 0, i = *offset;
|
|
i < stringLength && n < item->maxCount;
|
|
i++, n++)
|
|
{
|
|
if (!IsMemberOfCharacterClass(string[i],
|
|
item->data.multiChar.characters,
|
|
item->data.multiChar.nCharacters))
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (n >= item->minCount)
|
|
{
|
|
*offset = i;
|
|
return YES;
|
|
}
|
|
else
|
|
{
|
|
return NO;
|
|
}
|
|
}
|
|
break;
|
|
case AnyCharacterTextPatternItem:
|
|
{
|
|
unsigned int i, n;
|
|
|
|
for (i = *offset, n = 0; n < item->minCount; i++, n++)
|
|
{
|
|
if (i >= stringLength)
|
|
{
|
|
return NO;
|
|
}
|
|
}
|
|
|
|
*offset = i;
|
|
return YES;
|
|
}
|
|
break;
|
|
case BeginningOfWordTextPatternItem:
|
|
{
|
|
unsigned int i = *offset;
|
|
|
|
if (i >= stringLength)
|
|
{
|
|
return NO;
|
|
}
|
|
|
|
if (i > 0)
|
|
{
|
|
if (my_isalnum(string[i - 1]))
|
|
{
|
|
return NO;
|
|
}
|
|
else
|
|
{
|
|
return YES;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
return YES;
|
|
}
|
|
}
|
|
break;
|
|
case EndingOfWordTextPatternItem:
|
|
{
|
|
unsigned int i = *offset;
|
|
|
|
if (i >= stringLength)
|
|
{
|
|
return YES;
|
|
}
|
|
|
|
if (!my_isalnum(string[i]))
|
|
{
|
|
return YES;
|
|
}
|
|
else
|
|
{
|
|
return NO;
|
|
}
|
|
}
|
|
break;
|
|
case BeginningOfLineTextPatternItem:
|
|
{
|
|
unsigned int i = *offset;
|
|
|
|
if (i > 0)
|
|
{
|
|
return (string[i - 1] == '\n' || string[i - 1] == '\r');
|
|
}
|
|
else
|
|
{
|
|
return YES;
|
|
}
|
|
}
|
|
break;
|
|
case EndingOfLineTextPatternItem:
|
|
{
|
|
unsigned int i = *offset;
|
|
|
|
if (i + 1 < stringLength)
|
|
{
|
|
return (string[i + 1] == '\n' || string[i + 1] == '\r');
|
|
}
|
|
else
|
|
{
|
|
return YES;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
/* [NSException raise: NSInternalInconsistencyException
|
|
format: _(@"Unknown text pattern item type %i encountered."),
|
|
item->type];*/
|
|
|
|
return NO;
|
|
}
|
|
|
|
unsigned int
|
|
CheckTextPatternPresenceInString(TextPattern *pattern,
|
|
unichar *string,
|
|
unsigned int stringLength,
|
|
unsigned int index)
|
|
{
|
|
unsigned int i, off;
|
|
|
|
off = index;
|
|
|
|
for (i = 0; i < pattern->nItems; i++)
|
|
{
|
|
if (!CheckTextPatternItemPresence(pattern->items[i],
|
|
string,
|
|
stringLength,
|
|
&off))
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (i == pattern->nItems)
|
|
{
|
|
return off - index;
|
|
}
|
|
else
|
|
{
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
unichar *PermissibleCharactersAtPatternBeginning(TextPattern *pattern)
|
|
{
|
|
unsigned int i;
|
|
|
|
for (i = 0; i < pattern->nItems; i++)
|
|
{
|
|
switch(pattern->items[i]->type)
|
|
{
|
|
case SingleCharacterTextPatternItem:
|
|
{
|
|
unichar * buf;
|
|
|
|
buf = malloc(2 * sizeof(unichar));
|
|
buf[0] = pattern->items[i]->data.singleChar;
|
|
buf[1] = 0;
|
|
|
|
return buf;
|
|
}
|
|
case MultipleCharactersTextPatternItem:
|
|
{
|
|
unichar * buf;
|
|
unsigned int n = pattern->items[i]->data.multiChar.nCharacters + 1;
|
|
|
|
buf = malloc(n * sizeof(unichar));
|
|
memcpy(buf, pattern->items[i]->data.multiChar.characters, n *
|
|
sizeof(unichar));
|
|
buf[n - 1] = 0;
|
|
|
|
return buf;
|
|
}
|
|
case AnyCharacterTextPatternItem:
|
|
return (unichar *) -1;
|
|
|
|
default: break;
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|