Trying to sort a NSString using NSMutableArray

63 Views Asked by At

I have a NSString which holds info like this:

%hook APMIntro
- (bool)isIntroductoryOffer {
 return NO;
}
%end

%hook APMIntro
- (void)setIntroductoryOffer:(bool)arg1 {
 arg1 = NO;
 %orig;
}
%end

%hook ChallengeProgressHolder
- (void)setHasItem:(bool)arg1 {
    arg1 = NO;
    %orig;
}
%end

%hook ChallengeProgressHolder
- (bool)hasItem {
    return NO;
}
%end

The end result I'm trying to achieve is this

%hook APMIntro
- (bool)isIntroductoryOffer {
 return NO;
}

- (void)setIntroductoryOffer:(bool)arg1 {
 arg1 = NO;
 %orig;
}
%end

%hook ChallengeProgressHolder
- (void)setHasItem:(bool)arg1 {
    arg1 = NO;
    %orig;
}

- (bool)hasItem {
    return NO;
}
%end

So far, I've tried using NSMutableArray to separate the different lines to try and organize them - but the result is an infinite loop. I've tried this code so far

-(NSString *)cleanUp:(NSString *)cleanUp{
    NSMutableArray *content = [[cleanUp componentsSeparatedByString:@"%hook "] mutableCopy];
//  NSMutableArray *content = [[NSMutableArray alloc] init];
    NSMutableArray *cleaned = [[NSMutableArray alloc] init];
    NSMutableArray *hooks = [[NSMutableArray alloc] init];
//      NSArray *messy = [[cleanUp componentsSeparatedByString:@"\n"] mutableCopy];

    cleanUp = @"";
    NSString *line, *line1;

    //NSString *fixMe = @"";

    for (unsigned h = 1; h < content.count; h++){
        line = [content objectAtIndex:h-1];
        if ([line hasPrefix:@"#import"]){
            [cleaned addObject:[NSString stringWithFormat:@"%@\n", line]];
            continue;
        } else {
            cleanUp = [NSString stringWithFormat:@"%@%%hook %@", cleanUp, line];
            //[content addObject:[NSString stringWithFormat:@"%hook %@", line]];
        }
    }
    //NSLog(@"\n\n\n%@\n\n\n", cleanUp);
    BOOL lookingForEnd = NO;
    BOOL hookFound = NO;
    hooks = [[cleanUp componentsSeparatedByString:@"\n"] mutableCopy];
//  NSString *hook;
    //NSLog(@"\n\n\n%@\n\n\n", hooks);
    for (unsigned i = 1; i < hooks.count; i++){
        line = [hooks objectAtIndex:i-1];

        //NSLog(@"%i: %@\n\n", i, line);
        if (lookingForEnd) {
            [cleaned addObject:[NSString stringWithFormat:@"%@\n", line]];
            if ([line isEqualToString:@"%end"]){
                lookingForEnd = NO;
                //i = 1;
                continue;
            } else if ([line1 isEqualToString:line]){
                lookingForEnd = YES; 
            }
        }

        if ([line hasPrefix:@"%hook"]){
            line1 = line;
            for (unsigned j = 1; j < hooks.count; j++){
                printf("\n\n\nHOOK\n\n\n");
                if ([[hooks objectAtIndex:j-1] isEqual:line1]){
                    printf("\n\n\nFOUND\n\n\n");
                    [hooks addObject:line];
                    [cleaned addObject:[NSString stringWithFormat:@"%@\n", line]];
                    lookingForEnd = YES;
                    hookFound = YES;
                    //break;
                }
            }
        }
    }

    NSLog(@"%@\n", cleaned);

    return cleaned;
}

After setting this up and reading further into how to sort arrays - I found that I'm going about this all wrong - I'm just not sure how to go about doing it right.

Thanks in advance - any help is greatly appreciated

1

There are 1 best solutions below

1
DonMag On

You possibly could do this all with a complex RegEx -- but, here's a bit of a "step by step" approach.

Create a NSObject class with two string properties - blockName and blockBody.

We'll use this regular expression:

(^%hook )(.*?)(?=%end$)

to split the string into "pieces" of text contained between "%hook " and "%end".

Then, we'll loop through the matches creating an array of "block" objects, each with the name and body.

Next, we sort the array by blockName.

Finally, we loop through the sorted array, formatting an output string with the "bodies" grouped into the named blocks.

So, if we have:

MyBlock.h

@interface MyBlock : NSObject

@property (strong, nonatomic) NSString *blockName;
@property (strong, nonatomic) NSString *blockBody;

@end

MyBlock.m

#import "MyBlock.h"
@implementation MyBlock
@end

and we start with your sample NSString:

%hook APMIntro
- (bool)isIntroductoryOffer {
 return NO;
}
%end

%hook APMIntro
- (void)setIntroductoryOffer:(bool)arg1 {
 arg1 = NO;
 %orig;
}
%end

%hook ChallengeProgressHolder
- (void)setHasItem:(bool)arg1 {
    arg1 = NO;
    %orig;
}
%end

%hook ChallengeProgressHolder
- (bool)hasItem {
    return NO;
}
%end

We can do this:

NSString *input = [self sampleString];

NSMutableArray <MyBlock *> *blocks = [NSMutableArray new];

NSString *pattern = @"(^%hook )(.*?)(?=%end$)";
NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:pattern options:NSRegularExpressionDotMatchesLineSeparators | NSRegularExpressionAnchorsMatchLines error:NULL];

NSArray *myArray = [regex matchesInString:input options:0 range:NSMakeRange(0, [input length])] ;

for (NSTextCheckingResult *match in myArray) {
    // get the range starting after "%hook "
    NSRange matchRange = [match rangeAtIndex:2];
    // get the "block" string
    NSString *s = [input substringWithRange:matchRange];
    // split it by newLines
    NSArray <NSString *> *a = [s componentsSeparatedByString:@"\n"];
    // we want the "body" to skip the first line
    NSRange r;
    r.location = 1;
    r.length = [a count] - 1;
    // get the lines excluding the first line
    NSArray <NSString *> *a2 = [a subarrayWithRange:r];
    // new MyBlock object
    MyBlock *b = [MyBlock new];
    b.blockName = [a firstObject];
    b.blockBody = [a2 componentsJoinedByString:@"\n"];
    [blocks addObject:b];
}

// sort the array of blocks by blockName
NSArray <MyBlock *> *sortedArray;
sortedArray = [blocks sortedArrayUsingComparator:^NSComparisonResult(MyBlock *a, MyBlock *b) {
    return [a.blockName compare:b.blockName];
}];

NSMutableString *output = [NSMutableString new];
NSString *curName = @"";

// loop through the array of blocks
for (int i = 0; i < [sortedArray count]; i++) {
    MyBlock *b = sortedArray[i];
    // if we're at a "new" blockName
    if (![curName isEqualToString:b.blockName]) {
        curName = b.blockName;
        // add %end if output is not ""
        if ([output length] != 0) {
            [output appendString:@"%end\n"];
        }
        // add a new line of %hook blockName
        [output appendFormat:@"\n%%hook %@\n", curName];
    }
    // add the block body
    [output appendFormat:@"%@", b.blockBody];
}
// "close" the last block
[output appendString:@"%end\n"];

// log the resulting string
NSLog(@"%@", output);

and the output from that sample is:

%hook APMIntro
- (bool)isIntroductoryOffer {
    return NO;
}
- (void)setIntroductoryOffer:(bool)arg1 {
    arg1 = NO;
    %orig;
}
%end

%hook ChallengeProgressHolder
- (void)setHasItem:(bool)arg1 {
    arg1 = NO;
    %orig;
}
- (bool)hasItem {
    return NO;
}
%end

Play with the output formatting (line breaks, spacing, etc) as desired :)