// // P4TaggedDataTransformer.m // JobSuey // // Created by Work on 4/17/09. // Copyright 2009 Numerical Garden, LLC. All rights reserved. // #import "P4TaggedDataInflaterTransformer.h" #import "NGAUtilities.h" // Split a key into its base name and its index. i.e. for a key "how1,0" // the base name is "how" and they index is "1,0". static void SplitKey( NSString * key, NSString ** base, NSString ** index ); // Insert an element into the response structure. The element may need to // be inserted into an array nested deeply within the enclosing hash. static void InsertItem( NSMutableDictionary * hash, NSString * var, NSString * val ); @implementation P4TaggedDataInflaterTransformer SINGLETON_IMPLEMENTATION(P4TaggedDataInflaterTransformer, sharedInstance) + (Class)transformedValueClass { return [NSDictionary class]; } + (BOOL)allowsReverseTransformation { return NO; } -(NSDictionary*)transformedDictionary:(NSDictionary*)dictionary { if ( ![dictionary isKindOfClass:[NSDictionary class]]) return dictionary; NSMutableDictionary * hierarchy = [NSMutableDictionary dictionary]; for (NSString * key in [dictionary allKeys]) InsertItem( hierarchy, key, [dictionary objectForKey:key] ); return hierarchy; } - (id)transformedValue:(id)value { if ([value isKindOfClass:[NSDictionary class]]) return [self transformedDictionary:value]; if ([value isKindOfClass:[NSArray class]]) { NSMutableArray * transformedArray = [NSMutableArray arrayWithCapacity:[value count]]; for (NSDictionary * dictionary in value) { [transformedArray addObject:[self transformedDictionary:dictionary]]; } return transformedArray; } return nil; } @end void SplitKey( NSString * key, NSString ** base, NSString ** index ) { NSScanner * scanner = [NSScanner scannerWithString:key]; NSCharacterSet * keyCharacters = [NSCharacterSet characterSetWithCharactersInString:@",1234567890"]; if (![scanner scanUpToCharactersFromSet:keyCharacters intoString:base]) *base = nil; if (![scanner scanCharactersFromSet:keyCharacters intoString:index]) *index = nil; } void InsertItem( NSMutableDictionary * hash, NSString * var, NSString * val ) { NSString *key; NSString *base, *index; SplitKey( var, &base, &index ); // If there's no index, then we insert into the top level hash // but if the key is already defined then we need to rename the key. This // is probably one of those special keys like otherOpen which can be // both an array element and a scalar. The scalar comes last, so we // just rename it to "otherOpens" to avoid trashing the previous key // value if ( index == nil ) { key = var; if ( [hash doesContain:key] ) key = [key stringByAppendingString:@"s"]; [hash setObject:val forKey:key]; return; } // // Get or create the parent array from the hash. // key = base; NSMutableArray * ary = nil; NSMutableArray * tary = nil; ary = [hash objectForKey:key]; if ( nil == ary ) { ary = [NSMutableArray arrayWithCapacity:1]; [hash setObject:ary forKey:key]; } else if ( ![ary isKindOfClass:[NSArray class]] ) { // // There's an index in our var name, but the name is already defined // and the value it contains is not an array. This means we've got a // name collision. This can happen in 'p4 diff2' for example, when // one file gets 'depotFile' and the other gets 'depotFile2'. In // these cases it makes sense to keep the structure flat so we // just use the raw variable name. // [hash setObject:val forKey:var]; return; } // The index may be a simple digit, or it could be a comma separated // list of digits. For each "level" in the index, we need a containing // array. NSScanner * scanner = [NSScanner scannerWithString:index]; NSCharacterSet * commaSet = [NSCharacterSet characterSetWithCharactersInString:@","]; NSString * subIndexString = nil; while ( YES ) { [scanner scanUpToCharactersFromSet:commaSet intoString:&subIndexString]; [scanner scanCharactersFromSet:commaSet intoString:nil]; // skip comma NSUInteger subIndex = [subIndexString intValue]; // Found another level so we need to get/create a nested array // under the current entry. We use the level as an index so that // missing entries are left empty deliberately. if ( [scanner isAtEnd] ) { [ary NGA_setObject:val atIndex:subIndex]; break; } if ( [ary count] > subIndex ) tary = [ary objectAtIndex:subIndex]; else tary = nil; if ( !tary ) { tary = [NSMutableArray arrayWithCapacity:subIndex+1]; [ary NGA_setObject:tary atIndex:subIndex]; } ary = tary; } }