// // P4TaggedDataTransformer.m // JobSuey // // Created by Work on 4/17/09. // Copyright 2009 __MyCompanyName__. All rights reserved. // #import "P4TaggedDataTransformer.h" #import "NGAArrayAdditions.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". 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; } // // Insert an element into the response structure. The element may need to // be inserted into an array nested deeply within the enclosing hash. // void InsertItem( NSMutableDictionary * hash, NSString * var, NSString * val ) { NSString *key; // ID idLength = rb_intern( "length" ); NSString *base, *index; // NSString *comma = @","; 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 ) { // ID idHasKey = rb_intern( "has_key?"); // ID idPlus = rb_intern( "+" ); // key = rb_str_new2( var->Text() ); key = var; // if ( rb_funcall( hash, idHasKey, 1, key ) == Qtrue ) // key = rb_funcall( key, idPlus, 1, rb_str_new2( "s" ) ); if ( [hash doesContain:key] ) key = [key stringByAppendingString:@"s"]; // if( P4RDB_DATA ) // fprintf( stderr, "... %s -> %s\n", STR2CSTR( key ), val->Text() ); // rb_hash_aset( hash, key, rb_str_new2( val->Text() ) ); [hash setObject:val forKey:key]; return; } // // Get or create the parent array from the hash. // // key = rb_str_new2( base.Text() ); key = base; // ary = rb_hash_aref( hash, key ); NSMutableArray * ary = nil; NSMutableArray * tary = nil; ary = [hash objectForKey:key]; if ( nil == ary ) { // ary = rb_ary_new(); ary = [NSMutableArray arrayWithCapacity:1]; // rb_hash_aset( hash, key, ary ); [hash setObject:ary forKey:key]; } // else if( rb_obj_is_kind_of( ary, rb_cArray ) != Qtrue ) 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. // // if( P4RDB_DATA ) // fprintf( stderr, "... %s -> %s\n", var->Text(), val->Text() ); // rb_hash_aset( hash, rb_str_new2( var->Text() ) , // rb_str_new2( val->Text() ) ); [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. // if( P4RDB_DATA ) // fprintf( stderr, "... %s -> [", base.Text() ); NSScanner * scanner = [NSScanner scannerWithString:index]; NSCharacterSet * commaSet = [NSCharacterSet characterSetWithCharactersInString:@","]; // for( const char *c = 0 ; ( c = index.Contains( comma ) ); ) NSString * subIndexString = nil; while ( YES ) { [scanner scanUpToCharactersFromSet:commaSet intoString:&subIndexString]; [scanner scanCharactersFromSet:commaSet intoString:nil]; // skip comma NSUInteger subIndex = [subIndexString intValue]; // StrBuf level; // level.Set( index.Text(), c - index.Text() ); // index.Set( c + 1 ); // 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 setObject:val atIndex:subIndex]; // if( P4RDB_DATA ) // fprintf( stderr, "%d] = %s\n", pos, val->Text() ); break; } // tary = rb_ary_entry( ary, level.Atoi() ); if ( [ary count] > subIndex ) tary = [ary objectAtIndex:subIndex]; else tary = nil; // if ( ! RTEST( tary ) ) // { // tary = rb_ary_new(); // rb_ary_store( ary, level.Atoi(), tary ); // } if ( !tary ) { tary = [NSMutableArray arrayWithCapacity:subIndex+1]; [ary setObject:tary atIndex:subIndex]; } // if( P4RDB_DATA ) // fprintf( stderr, "%s][", level.Text() ); ary = tary; } // int pos = index.Atoi(); // if( P4RDB_DATA ) // fprintf( stderr, "%d] = %s\n", pos, val->Text() ); // rb_ary_store( ary, pos, rb_str_new2( val->Text() ) ); } @implementation P4TaggedDataTransformer + (Class)transformedValueClass { return [NSDictionary class]; } + (BOOL)allowsReverseTransformation { return NO; } -(NSDictionary*)transformedDictionary:(NSDictionary*)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; } //- (id)transformedValue:(id)value //{ // if (![value isKindOfClass:[NSDictionary class]]) // return nil; // // NSDictionary * input = (NSDictionary*)value; // NSMutableDictionary * hierarchy = [NSMutableDictionary dictionaryWithCapacity:1]; // // // convert perforce notation into a hierarchical structure for binding processing // // Here's the language // // , // // So file0,77 becomes: // // file[0][77] // // where "file" is a key in a dictionary which refers to an array // // we get item 0 in the array which is a subarray // // then we get item 77 in the subarray // // // // THIS CODE ONLY HANDLES A HIERARCHY ONE LEVEL DEEP // // NSCharacterSet * numberChars = [NSCharacterSet decimalDigitCharacterSet]; // for ( NSString * rawKey in [input allKeys] ) // { // id value = [input valueForKey:rawKey]; // // NSRange range = [rawKey rangeOfCharacterFromSet:numberChars]; // if ( range.location == NSNotFound ) // { // [hierarchy setObject:value forKey:rawKey]; // continue; // } // // NSString * key = [rawKey substringToIndex:range.location]; // NSInteger index = [[rawKey substringFromIndex:range.location] intValue]; // // NSMutableArray * array = [hierarchy objectForKey:key]; // if ( !array ) // { // array = [NSMutableArray arrayWithCapacity:1]; // [hierarchy setObject:array forKey:key]; // } // // // resize array if necessary // if ( index >= [array count] ) // { // int numberOfExtraItems = index + 1 - [array count]; // for (int i = 0; i < numberOfExtraItems; i++) // [array addObject:[NSNull null]]; // } // [array replaceObjectAtIndex:index withObject:value]; // } // // return hierarchy; //} //- (id)reverseTransformedValue:(id)value //{ // if (![value isKindOfClass:[NSDictionary class]]) // return nil; // // NSDictionary * input = (NSDictionary*)value; // NSMutableDictionary * flattened = [NSMutableDictionary dictionaryWithCapacity:1]; // // for (NSString * key in [input allKeys]) // { // id value = [input objectForKey:key]; // if ( ![value isKindOfClass:[NSArray class]] ) // { // [flattened setObject:value forKey:key]; // continue; // } // // NSArray * array = (NSArray*)value; // for (int i = 0; i < [array count]; i++) // { // [flattened setObject:[array objectAtIndex:i] // forKey:[NSString stringWithFormat:@"%@%d", key, i]]; // } // } // return flattened; //} @end