aboutsummaryrefslogtreecommitdiffstats
path: root/Source/SPSQLParser.m
diff options
context:
space:
mode:
Diffstat (limited to 'Source/SPSQLParser.m')
-rw-r--r--Source/SPSQLParser.m655
1 files changed, 655 insertions, 0 deletions
diff --git a/Source/SPSQLParser.m b/Source/SPSQLParser.m
new file mode 100644
index 00000000..9828f529
--- /dev/null
+++ b/Source/SPSQLParser.m
@@ -0,0 +1,655 @@
+//
+// SPSQLParsing.m
+// sequel-pro
+//
+// Created by Rowan Beentje on 18/01/2009.
+// Copyright 2009 Rowan Beentje. All rights reserved.
+//
+// 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+//
+// More info at <http://code.google.com/p/sequel-pro/>
+
+#import "SPSQLParser.h"
+
+/*
+ * Please see the header files for a general description of the purpose of this class,
+ * and increased overview detail for the functions below.
+ */
+@implementation SPSQLParser : NSMutableString
+
+
+
+/*
+ * Removes comments within the current string, trimming "#", "--[/s]", and "/* * /" style strings.
+ */
+- (void) deleteComments
+{
+ long currentStringIndex, commentEndIndex, quotedStringEndIndex;
+ unichar currentCharacter;
+ long stringLength = [string length];
+
+ // Walk along the string, processing characters.
+ for (currentStringIndex = 0; currentStringIndex < stringLength; currentStringIndex++) {
+ currentCharacter = [string characterAtIndex:currentStringIndex];
+ switch (currentCharacter) {
+
+ // When quote characters are encountered walk to the end of the quoted string.
+ case '\'':
+ case '"':
+ case '`':
+ quotedStringEndIndex = [self endIndexOfStringQuotedByCharacter:currentCharacter startingAtIndex:currentStringIndex+1];
+ if (quotedStringEndIndex == NSNotFound) {
+ return;
+ }
+ currentStringIndex = quotedStringEndIndex;
+ break;
+
+ // For comments starting "--[\s]", ensure the start syntax is valid before proceeding.
+ case '-':
+ if (stringLength < currentStringIndex + 2) break;
+ if ([string characterAtIndex:currentStringIndex+1] != '-') break;
+ if (![[NSCharacterSet whitespaceAndNewlineCharacterSet] characterIsMember:[string characterAtIndex:currentStringIndex+2]]) break;
+ commentEndIndex = [self endIndexOfCommentOfType:SPDoubleDashComment startingAtIndex:currentStringIndex];
+
+ // Remove the comment
+ [string deleteCharactersInRange:NSMakeRange(currentStringIndex, commentEndIndex - currentStringIndex + 1)];
+ stringLength -= commentEndIndex - currentStringIndex + 1;
+ currentStringIndex--;
+ break;
+
+ case '#':
+ commentEndIndex = [self endIndexOfCommentOfType:SPHashComment startingAtIndex:currentStringIndex];
+
+ // Remove the comment
+ [string deleteCharactersInRange:NSMakeRange(currentStringIndex, commentEndIndex - currentStringIndex + 1)];
+ stringLength -= commentEndIndex - currentStringIndex + 1;
+ currentStringIndex--;
+ break;
+
+ // For comments starting "/*", ensure the start syntax is valid before proceeding.
+ case '/':
+ if (stringLength < currentStringIndex + 1) break;
+ if ([string characterAtIndex:currentStringIndex+1] != '*') break;
+ commentEndIndex = [self endIndexOfCommentOfType:SPCStyleComment startingAtIndex:currentStringIndex];
+
+ // Remove the comment
+ [string deleteCharactersInRange:NSMakeRange(currentStringIndex, commentEndIndex - currentStringIndex + 1)];
+ stringLength -= commentEndIndex - currentStringIndex + 1;
+ currentStringIndex--;
+ break;
+ }
+ }
+}
+
+
+/*
+ * Removes quotes surrounding the string if present, and un-escapes internal occurrences of the quote character before returning.
+ */
+- (NSString *) unquotedString
+{
+ NSMutableString *returnString;
+ long stringEndIndex;
+ unichar quoteCharacter;
+
+ if (![string length]) return nil;
+
+ // If the first character is not a quote character, return the entire string.
+ quoteCharacter = [string characterAtIndex:0];
+ if (quoteCharacter != '`' && quoteCharacter != '"' && quoteCharacter != '\'') {
+ return [NSString stringWithString:string];
+ }
+
+ // Get the end of the string
+ stringEndIndex = [self endIndexOfStringQuotedByCharacter:quoteCharacter startingAtIndex:1];
+ if (stringEndIndex == NSNotFound) {
+ return [NSString stringWithString:string];
+ }
+
+ // Trim the string appropriately
+ returnString = [NSMutableString stringWithString:[string substringWithRange:NSMakeRange(1, stringEndIndex-1)]];
+
+ // Remove escaped characters and escaped strings as appropriate
+ if (quoteCharacter == '`' || quoteCharacter == '"' || quoteCharacter == '\'') {
+ [returnString replaceOccurrencesOfString:[NSString stringWithFormat:@"%C%C", quoteCharacter, quoteCharacter] withString:[NSString stringWithFormat:@"%C", quoteCharacter] options:0 range:NSMakeRange(0, [returnString length])];
+ }
+ if (quoteCharacter == '"') {
+ [returnString replaceOccurrencesOfString:@"\\\"" withString:@"\"" options:0 range:NSMakeRange(0, [returnString length])];
+ [returnString replaceOccurrencesOfString:@"\\\\" withString:@"\\" options:0 range:NSMakeRange(0, [returnString length])];
+ } else if (quoteCharacter == '\'') {
+ [returnString replaceOccurrencesOfString:@"\\'" withString:@"'" options:0 range:NSMakeRange(0, [returnString length])];
+ [returnString replaceOccurrencesOfString:@"\\\\" withString:@"\\" options:0 range:NSMakeRange(0, [returnString length])];
+ }
+
+ return returnString;
+}
+
+
+/*
+ * Removes characters from the string up to the first occurrence of the supplied character.
+ */
+- (BOOL) trimToCharacter:(unichar)character inclusively:(BOOL)inclusive
+{
+ return [self trimToCharacter:character inclusively:inclusive ignoringQuotedStrings:YES];
+}
+
+
+/*
+ * As trimToCharacter: ..., but allows control over whether characters within quoted
+ * strings are ignored.
+ */
+- (BOOL) trimToCharacter:(unichar)character inclusively:(BOOL)inclusive ignoringQuotedStrings:(BOOL)ignoreQuotedStrings
+{
+ long stringIndex;
+
+ // Get the first occurrence of the specified character, returning NO if not found
+ stringIndex = [self firstOccurrenceOfCharacter:character ignoringQuotedStrings:ignoreQuotedStrings];
+ if (stringIndex == NSNotFound) return NO;
+
+ // If it has been found, trim the string appropriately and return YES
+ [string deleteCharactersInRange:NSMakeRange(0, stringIndex + (inclusive?1:0))];
+ return YES;
+}
+
+
+/*
+ * Returns an NSString containing characters from the string up to the first occurrence of the supplied character.
+ */
+- (NSString *) stringToCharacter:(unichar)character inclusively:(BOOL)inclusive
+{
+ return [self stringToCharacter:character inclusively:inclusive ignoringQuotedStrings:YES];
+}
+
+
+/*
+ * As stringToCharacter: ..., but allows control over whether characters within quoted strings
+ * are ignored.
+ */
+- (NSString *) stringToCharacter:(unichar)character inclusively:(BOOL)inclusive ignoringQuotedStrings:(BOOL)ignoreQuotedStrings {
+ long stringIndex;
+
+ // Get the first occurrence of the specified character, returning nil if not found
+ stringIndex = [self firstOccurrenceOfCharacter:character ignoringQuotedStrings:ignoreQuotedStrings];
+ if (stringIndex == NSNotFound) return nil;
+
+ // If it has been found, return the appropriate string range
+ return [string substringWithRange:NSMakeRange(0, stringIndex + (inclusive?1:0))];
+}
+
+
+/*
+ * Returns an NSString containing characters from the string up to the first occurrence of the supplied
+ * character, also removing them from the string.
+ */
+- (NSString *) trimAndReturnStringToCharacter:(unichar)character trimmingInclusively:(BOOL)inclusiveTrim returningInclusively:(BOOL)inclusiveReturn
+{
+ return [self trimAndReturnStringToCharacter:character trimmingInclusively:inclusiveTrim returningInclusively:inclusiveReturn ignoringQuotedStrings:YES];
+}
+
+
+/*
+ * As trimAndReturnStringToCharacter: ..., but allows control over whether characters within quoted
+ * strings are ignored.
+ */
+- (NSString *) trimAndReturnStringToCharacter:(unichar)character trimmingInclusively:(BOOL)inclusiveTrim returningInclusively:(BOOL)inclusiveReturn ignoringQuotedStrings:(BOOL)ignoreQuotedStrings
+{
+ long stringIndex;
+ NSString *resultString;
+
+ // Get the first occurrence of the specified character, returning nil if it could not be found
+ stringIndex = [self firstOccurrenceOfCharacter:character ignoringQuotedStrings:ignoreQuotedStrings];
+ if (stringIndex == NSNotFound) return nil;
+
+ // Select the appropriate string range, truncate the current string, and return the selected string
+ resultString = [NSString stringWithString:[string substringWithRange:NSMakeRange(0, stringIndex + (inclusiveReturn?1:0))]];
+ [string deleteCharactersInRange:NSMakeRange(0, stringIndex + (inclusiveTrim?1:0))];
+ return resultString;
+}
+
+
+/*
+ * Returns characters from the string up to and from the first occurrence of the supplied opening character
+ * to the appropriate occurrence of the supplied closing character. "inclusively" controls whether the supplied
+ * characters should also be returned.
+ */
+- (NSString *) stringFromCharacter:(unichar)fromCharacter toCharacter:(unichar)toCharacter inclusively:(BOOL)inclusive
+{
+ return [self stringFromCharacter:fromCharacter toCharacter:toCharacter inclusively:inclusive skippingBrackets:NO ignoringQuotedStrings:YES];
+}
+
+
+/*
+ * As stringFromCharacter: toCharacter: ..., but allows control over whether to skip
+ * over bracket-enclosed characters, as in subqueries, enums, definitions or groups
+ */
+- (NSString *) stringFromCharacter:(unichar)fromCharacter toCharacter:(unichar)toCharacter inclusively:(BOOL)inclusive skippingBrackets:(BOOL)skipBrackets
+{
+ return [self stringFromCharacter:fromCharacter toCharacter:toCharacter inclusively:inclusive skippingBrackets:skipBrackets ignoringQuotedStrings:YES];
+}
+
+
+/*
+ * As stringFromCharacter: toCharacter: ..., but allows control over whether characters within quoted
+ * strings are ignored.
+ */
+- (NSString *) stringFromCharacter:(unichar)fromCharacter toCharacter:(unichar)toCharacter inclusively:(BOOL)inclusive ignoringQuotedStrings:(BOOL)ignoreQuotedStrings
+{
+ return [self stringFromCharacter:fromCharacter toCharacter:toCharacter inclusively:inclusive skippingBrackets:NO ignoringQuotedStrings:ignoreQuotedStrings];
+}
+
+
+/*
+ * As stringFromCharacter: toCharacter: ..., but allows control over both bracketing and quoting.
+ */
+- (NSString *) stringFromCharacter:(unichar)fromCharacter toCharacter:(unichar)toCharacter inclusively:(BOOL)inclusive skippingBrackets:(BOOL)skipBrackets ignoringQuotedStrings:(BOOL)ignoreQuotedStrings
+{
+ long fromCharacterIndex, toCharacterIndex;
+
+ // Look for the first occurrence of the from: character
+ fromCharacterIndex = [self firstOccurrenceOfCharacter:fromCharacter afterIndex:-1 skippingBrackets:skipBrackets ignoringQuotedStrings:ignoreQuotedStrings];
+ if (fromCharacterIndex == NSNotFound) return nil;
+
+ // Look for the first/balancing occurrence of the to: character
+ toCharacterIndex = [self firstOccurrenceOfCharacter:toCharacter afterIndex:fromCharacterIndex skippingBrackets:skipBrackets ignoringQuotedStrings:ignoreQuotedStrings];
+ if (toCharacterIndex == NSNotFound) return nil;
+
+ // Return the correct part of the string.
+ return [string substringWithRange:NSMakeRange(fromCharacterIndex + (inclusive?0:1), toCharacterIndex + (inclusive?1:-1) - fromCharacterIndex)];
+}
+
+
+/*
+ * As stringFromCharacter: toCharacter: ..., but also trims the string up to the "to" character and
+ * up to or including the "from" character, depending on whether "trimmingInclusively" is set.
+ */
+- (NSString *) trimAndReturnStringFromCharacter:(unichar)fromCharacter toCharacter:(unichar)toCharacter trimmingInclusively:(BOOL)inclusiveTrim returningInclusively:(BOOL)inclusiveReturn
+{
+ return [self trimAndReturnStringFromCharacter:fromCharacter toCharacter:toCharacter trimmingInclusively:inclusiveTrim returningInclusively:inclusiveReturn skippingBrackets:NO ignoringQuotedStrings:YES];
+}
+
+
+/*
+ * As trimAndReturnStringFromCharacter: toCharacter: ..., but allows control over whether to
+ * skip over bracket-enclosed characters, as in subqueries, enums, definitions or groups.
+ */
+- (NSString *) trimAndReturnStringFromCharacter:(unichar)fromCharacter toCharacter:(unichar)toCharacter trimmingInclusively:(BOOL)inclusiveTrim returningInclusively:(BOOL)inclusiveReturn skippingBrackets:(BOOL)skipBrackets
+{
+ return [self trimAndReturnStringFromCharacter:fromCharacter toCharacter:toCharacter trimmingInclusively:inclusiveTrim returningInclusively:inclusiveReturn skippingBrackets:skipBrackets ignoringQuotedStrings:YES];
+}
+
+
+/*
+ * As trimAndReturnStringFromCharacter: toCharacter: ..., but allows control over whether characters
+ * within quoted strings are ignored.
+ */
+- (NSString *) trimAndReturnStringFromCharacter:(unichar)fromCharacter toCharacter:(unichar)toCharacter trimmingInclusively:(BOOL)inclusiveTrim returningInclusively:(BOOL)inclusiveReturn ignoringQuotedStrings:(BOOL)ignoreQuotedStrings
+{
+ return [self trimAndReturnStringFromCharacter:fromCharacter toCharacter:toCharacter trimmingInclusively:inclusiveTrim returningInclusively:inclusiveReturn skippingBrackets:NO ignoringQuotedStrings:ignoreQuotedStrings];
+}
+
+
+/*
+ * As trimAndReturnStringFromCharacter: toCharacter: ..., but allows control over both bracketing
+ * and quoting.
+ */
+- (NSString *) trimAndReturnStringFromCharacter:(unichar)fromCharacter toCharacter:(unichar)toCharacter trimmingInclusively:(BOOL)inclusiveTrim returningInclusively:(BOOL)inclusiveReturn skippingBrackets:(BOOL)skipBrackets ignoringQuotedStrings:(BOOL)ignoreQuotedStrings
+{
+ long fromCharacterIndex, toCharacterIndex;
+ NSString *resultString;
+
+ // Look for the first occurrence of the from: character
+ fromCharacterIndex = [self firstOccurrenceOfCharacter:fromCharacter afterIndex:-1 skippingBrackets:skipBrackets ignoringQuotedStrings:ignoreQuotedStrings];
+ if (fromCharacterIndex == NSNotFound) return nil;
+
+ // Look for the first/balancing occurrence of the to: character
+ toCharacterIndex = [self firstOccurrenceOfCharacter:toCharacter afterIndex:fromCharacterIndex skippingBrackets:skipBrackets ignoringQuotedStrings:ignoreQuotedStrings];
+ if (toCharacterIndex == NSNotFound) return nil;
+
+ // Select the correct part of the string, truncate the current string, and return the selected string.
+ resultString = [string substringWithRange:NSMakeRange(fromCharacterIndex + (inclusiveReturn?0:1), toCharacterIndex + (inclusiveReturn?1:-1) - fromCharacterIndex)];
+ [string deleteCharactersInRange:NSMakeRange(fromCharacterIndex + (inclusiveTrim?0:1), toCharacterIndex + (inclusiveTrim?1:-1) - fromCharacterIndex)];
+ return resultString;
+}
+
+/*
+ * Split a string on the boundaries formed by the supplied character, returning an array of strings.
+ */
+- (NSArray *) splitStringByCharacter:(unichar)character
+{
+ return [self splitStringByCharacter:character skippingBrackets:NO ignoringQuotedStrings:YES];
+}
+
+/*
+ * As splitStringByCharacter: ..., but allows control over whether to skip over bracket-enclosed
+ * characters, as in subqueries, enums, definitions or groups.
+ */
+- (NSArray *) splitStringByCharacter:(unichar)character skippingBrackets:(BOOL)skipBrackets
+{
+ return [self splitStringByCharacter:character skippingBrackets:skipBrackets ignoringQuotedStrings:YES];
+}
+
+
+/*
+ * As splitStringByCharacter:, but allows control over whether characters
+ * within quoted strings are ignored.
+ */
+- (NSArray *) splitStringByCharacter:(unichar)character ignoringQuotedStrings:(BOOL)ignoreQuotedStrings
+{
+ return [self splitStringByCharacter:character skippingBrackets:NO ignoringQuotedStrings:ignoreQuotedStrings];
+}
+
+
+/*
+ * As splitStringByCharacter: ..., but allows control over both bracketing and quoting.
+ */
+- (NSArray *) splitStringByCharacter:(unichar)character skippingBrackets:(BOOL)skipBrackets ignoringQuotedStrings:(BOOL)ignoreQuotedStrings
+{
+ NSMutableArray *resultsArray = [NSMutableArray array];
+ long stringIndex = -1, nextIndex = 0;
+
+ // Walk through the string finding the character to split by, and add non-zero length strings.
+ while (1) {
+ nextIndex = [self firstOccurrenceOfCharacter:character afterIndex:stringIndex skippingBrackets:skipBrackets ignoringQuotedStrings:ignoreQuotedStrings];
+ if (nextIndex == NSNotFound) {
+ break;
+ }
+
+ if (nextIndex - stringIndex - 1 > 0) {
+ [resultsArray addObject:[string substringWithRange:NSMakeRange(stringIndex+1, nextIndex - stringIndex - 1)]];
+ }
+ stringIndex = nextIndex;
+ }
+
+ // Add the end of the string after the previously matched character where appropriate.
+ if (stringIndex + 1 < [string length]) {
+ [resultsArray addObject:[string substringWithRange:NSMakeRange(stringIndex+1, [string length] - stringIndex - 1)]];
+ }
+
+ return resultsArray;
+}
+
+
+/*
+ * A method intended for use by the functions above.
+ */
+- (long) firstOccurrenceOfCharacter:(unichar)character ignoringQuotedStrings:(BOOL)ignoreQuotedStrings
+{
+ return [self firstOccurrenceOfCharacter:character afterIndex:-1 skippingBrackets:NO ignoringQuotedStrings:ignoreQuotedStrings];
+}
+
+
+/*
+ * A method intended for use by the functions above.
+ */
+- (long) firstOccurrenceOfCharacter:(unichar)character afterIndex:(long)startIndex ignoringQuotedStrings:(BOOL)ignoreQuotedStrings
+{
+ return [self firstOccurrenceOfCharacter:character afterIndex:startIndex skippingBrackets:NO ignoringQuotedStrings:ignoreQuotedStrings];
+}
+
+
+/*
+ * A method intended for use by the functions above.
+ */
+- (long) firstOccurrenceOfCharacter:(unichar)character afterIndex:(long)startIndex skippingBrackets:(BOOL)skipBrackets ignoringQuotedStrings:(BOOL)ignoreQuotedStrings
+{
+ long currentStringIndex, quotedStringEndIndex;
+ unichar currentCharacter;
+ long stringLength = [string length];
+ int bracketingLevel = 0;
+
+ // Sanity check inputs
+ if (startIndex < -1) startIndex = -1;
+
+ // Walk along the string, processing characters
+ for (currentStringIndex = startIndex + 1; currentStringIndex < stringLength; currentStringIndex++) {
+ currentCharacter = [string characterAtIndex:currentStringIndex];
+
+ // Check for the ending character, and if it has been found and quoting/brackets is valid, return.
+ if (currentCharacter == character) {
+ if (!skipBrackets || bracketingLevel <= 0) {
+ return currentStringIndex;
+ }
+ }
+
+ // Process strings and comments as appropriate
+ switch (currentCharacter) {
+
+ // When quote characters are encountered and strings are not being ignored, walk to the end of the quoted string.
+ case '\'':
+ case '"':
+ case '`':
+ if (!ignoreQuotedStrings) break;
+ quotedStringEndIndex = [self endIndexOfStringQuotedByCharacter:currentCharacter startingAtIndex:currentStringIndex+1];
+ if (quotedStringEndIndex == NSNotFound) {
+ return NSNotFound;
+ }
+ currentStringIndex = quotedStringEndIndex;
+ break;
+
+ // For opening brackets increment the bracket count
+ case '(':
+ bracketingLevel++;
+ break;
+
+ // For closing brackets decrement the bracket count
+ case ')':
+ bracketingLevel--;
+
+ // For comments starting "--[\s]", ensure the start syntax is valid before proceeding.
+ case '-':
+ if (stringLength < currentStringIndex + 2) break;
+ if ([string characterAtIndex:currentStringIndex+1] != '-') break;
+ if (![[NSCharacterSet whitespaceAndNewlineCharacterSet] characterIsMember:[string characterAtIndex:currentStringIndex+2]]) break;
+ currentStringIndex = [self endIndexOfCommentOfType:SPDoubleDashComment startingAtIndex:currentStringIndex];
+ break;
+
+ case '#':
+ currentStringIndex = [self endIndexOfCommentOfType:SPHashComment startingAtIndex:currentStringIndex];
+ break;
+
+ // For comments starting "/*", ensure the start syntax is valid before proceeding.
+ case '/':
+ if (stringLength < currentStringIndex + 1) break;
+ if ([string characterAtIndex:currentStringIndex+1] != '*') break;
+ currentStringIndex = [self endIndexOfCommentOfType:SPCStyleComment startingAtIndex:currentStringIndex];
+ break;
+ }
+ }
+
+ // If no matches have been made in this string, return NSNotFound.
+ return NSNotFound;
+}
+
+/*
+ * A method intended for use by the functions above.
+ */
+- (long) endIndexOfStringQuotedByCharacter:(unichar)quoteCharacter startingAtIndex:(long)index
+{
+ long currentStringIndex, stringLength, i, quotedStringLength;
+ BOOL characterIsEscaped;
+ unichar currentCharacter;
+
+ stringLength = [string length];
+
+ // Walk the string looking for the string end
+ for ( currentStringIndex = index; currentStringIndex < stringLength; currentStringIndex++) {
+ currentCharacter = [string characterAtIndex:currentStringIndex];
+
+ // If the string end is a backtick and one has been encountered, treat it as end of string
+ if (quoteCharacter == '`' && currentCharacter == '`') {
+
+ // ...as long as the next character isn't also a backtick, in which case it's being quoted. Skip both.
+ if ((currentStringIndex + 1) < stringLength && [string characterAtIndex:currentStringIndex+1] == '`') {
+ currentStringIndex++;
+ continue;
+ }
+
+ return currentStringIndex;
+
+ // Otherwise, prepare to treat the string as ended when meeting the correct boundary character....
+ } else if (currentCharacter == quoteCharacter) {
+
+ // ...but only if the string end isn't escaped with an *odd* number of escaping characters...
+ characterIsEscaped = NO;
+ i = 1;
+ quotedStringLength = currentStringIndex - 1;
+ while ((quotedStringLength - i) > 0 && [string characterAtIndex:currentStringIndex - i] == '\\') {
+ characterIsEscaped = !characterIsEscaped;
+ i++;
+ }
+
+ // If an even number have been found, it may be the end of the string - as long as the subsequent character
+ // isn't also the same character, in which case it's another form of escaping.
+ if (!characterIsEscaped) {
+ if ((currentStringIndex + 1) < stringLength && [string characterAtIndex:currentStringIndex+1] == quoteCharacter) {
+ currentStringIndex++;
+ continue;
+ }
+
+ // Really is the end of the string.
+ return currentStringIndex;
+ }
+ }
+ }
+
+ return NSNotFound;
+}
+
+/*
+ * A method intended for use by the functions above.
+ */
+- (long) endIndexOfCommentOfType:(SPCommentType)commentType startingAtIndex:(long)index
+{
+ long stringLength = [string length];
+ unichar currentCharacter;
+
+ switch (commentType) {
+
+ // For comments of type "--[\s]", start the comment processing two characters in to match the start syntax,
+ // then flow into the Hash comment handling (looking for first newline).
+ case SPDoubleDashComment:
+ index = index+2;
+
+ // For comments starting "--[\s]" and "#", continue until the first newline.
+ case SPHashComment:
+ index++;
+ for ( ; index < stringLength; index++ ) {
+ currentCharacter = [string characterAtIndex:index];
+ if (currentCharacter == '\r' || currentCharacter == '\n') {
+ return index-1;
+ }
+ }
+ break;
+
+ // For comments starting "/*", start the comment processing one character in to match the start syntax, then
+ // continue until the first matching "*/".
+ case SPCStyleComment:
+ index = index+2;
+ for ( ; index < stringLength; index++ ) {
+ if ([string characterAtIndex:index] == '*') {
+ if ((stringLength > index + 1) && [string characterAtIndex:index+1] == '/') {
+ return (index+1);
+ }
+ }
+ }
+ }
+
+ // If no match has been found, the comment must continue until the very end of the string.
+ return (stringLength-1);
+}
+
+
+/* Required and primitive methods to allow subclassing class cluster */
+#pragma mark -
+- (id) init {
+ if (self = [super init]) {
+ string = [[NSMutableString string] retain];
+ }
+ return self;
+}
+- (id) initWithBytes:(const void *)bytes length:(unsigned int)length encoding:(NSStringEncoding)encoding {
+ if (self = [super init]) {
+ string = [[NSMutableString alloc] initWithBytes:bytes length:length encoding:encoding];
+ }
+ return self;
+}
+- (id) initWithBytesNoCopy:(void *)bytes length:(unsigned int)length encoding:(NSStringEncoding)encoding freeWhenDone:(BOOL)flag {
+ if (self = [super init]) {
+ string = [[NSMutableString alloc] initWithBytesNoCopy:bytes length:length encoding:encoding freeWhenDone:flag];
+ }
+ return self;
+}
+- (id) initWithCapacity:(unsigned int)capacity {
+ if (self = [super init]) {
+ string = [[NSMutableString stringWithCapacity:capacity] retain];
+ }
+ return self;
+}
+- (id) initWithCharactersNoCopy:(unichar *)characters length:(unsigned int)length freeWhenDone:(BOOL)flag {
+ if (self = [super init]) {
+ string = [[NSMutableString alloc] initWithCharactersNoCopy:characters length:length freeWhenDone:flag];
+ }
+ return self;
+}
+- (id) initWithContentsOfFile:(id)path {
+ return [self initWithContentsOfFile:path encoding:NSUTF8StringEncoding error:NULL];
+}
+- (id) initWithContentsOfFile:(NSString *)path encoding:(NSStringEncoding)encoding error:(NSError **)error {
+ if (self = [super init]) {
+ string = [[NSMutableString alloc] initWithContentsOfFile:path encoding:encoding error:error];
+ }
+ return self;
+}
+- (id) initWithCString:(const char *)nullTerminatedCString encoding:(NSStringEncoding)encoding {
+ if (self = [super init]) {
+ string = [[NSMutableString alloc] initWithCString:nullTerminatedCString encoding:encoding];
+ }
+ return self;
+}
+- (id) initWithFormat:(NSString *)format, ... {
+ va_list argList;
+ va_start(argList, format);
+ id str = [self initWithFormat:format arguments:argList];
+ va_end(argList);
+ return str;
+}
+- (id) initWithFormat:(NSString *)format arguments:(va_list)argList {
+ if (self = [super init]) {
+ string = [[NSMutableString alloc] initWithFormat:format arguments:argList];
+ }
+ return self;
+}
+- (unsigned int) length {
+ return [string length];
+}
+- (unichar) characterAtIndex:(unsigned int)index {
+ return [string characterAtIndex:index];
+}
+- (id) description {
+ return [string description];
+}
+- (unsigned int) replaceOccurrencesOfString:(NSString *)target withString:(NSString *)replacement options:(unsigned)options range:(NSRange)searchRange {
+ return [string replaceOccurrencesOfString:target withString:replacement options:options range:searchRange];
+}
+- (void) setString:(NSString *)aString {
+ [string setString:aString];
+}
+- (void) replaceCharactersInRange:(NSRange)range withString:(NSString *)aString {
+ [string replaceCharactersInRange:range withString:aString];
+}
+- (void) dealloc {
+ [string release];
+ [super dealloc];
+}
+@end \ No newline at end of file