Best way to write arbitrary NSData into an NSXMLElement - objective-c

I am allowing for application data (it's a Mac app on 10.7) to be exported as an XML file, and one field I would like to be able to export/import to/from XML is an NSData field. What would be the correct/accepted way of doing this? Should I convert to base64 and write that string to XML?
I would prefer not to roll my own solution, using a category, as the accepted answer to the linked question does (linking to Matt Gallagher's solution).
Update
I just discovered the NSPropertyListSerialization class. I got my hopes up, but it only has static serialization methods which return NSData representations.

I realized (as my updated alluded to) that I could use the NSPropertyListSerialization class, since the NSData returned by -dataWithPropertyList:format:options:error: is just a UTF-8 string. This is what I'm using to serialize:
NSData *data = value;
NSError *error = nil;
NSData *plistData = [NSPropertyListSerialization dataWithPropertyList:data
format:NSPropertyListXMLFormat_v1_0
options:0
error:&error];
if (error) {
NSLog(#"Error serializing data to plist XML: %#", error);
} else {
NSString *plistString = [[NSString alloc] initWithData:plistData encoding:NSUTF8StringEncoding];
NSXMLElement *dataElement = [NSXMLElement elementWithName:field
stringValue:plistString];
}
And deserialize:
NSData *plistData = [element.stringValue dataUsingEncoding:NSUTF8StringEncoding];
NSData *originalData = [NSPropertyListSerialization propertyListWithData:plistData
options:NSPropertyListImmutable
format:NULL
error:&error];
if (error) {
NSLog(#"Error deserializing data from plist XML: %#", error);
} else {
value = originalData;
}

Related

Converting a JSON file to NSMutableDictionary in Objective C?

I have a json file that looks like this:
{
"data":
{
"level": [
{
//bunch of stuff
}
]
}
}
Now I want to convert that into a array of levels that I can access. If I take away the {"data: part, then I can use this:
NSData *allLevelsData = [[NSData alloc] initWithContentsOfFile:fileLoc];
NSError *error = nil;
NSMutableDictionary *allLevels = [NSJSONSerialization JSONObjectWithData:allLevelsData options:kNilOptions error:&error];
if(!error){
NSMutableArray *level = allLevels[#"level"];
for (NSMutableDictionary *aLevel in level){
//do stuff with the level...
But I have to have the {"data: as part of the file, and I can't figure out how to get a NSData object out of the existing NSData object. Any ideas?
Don't you need to pull the level NSArray out of the data NSDictionary first?
NSData *allLevelsData = [[NSData alloc] initWithContentsOfFile:fileLoc];
NSError *error = nil;
NSDictionary *dataDictionary = [NSJSONSerialization JSONObjectWithData:allLevelsData options:kNilOptions error:&error];
if(!error){
NSArray *levels = dataDictionary[#"data"][#"level"];
for (NSDictionary *aLevel in levels){
//do stuff with the level...
You won't get mutable objects back by default and declaring the variables as mutable doesn't make them so. Take a mutableCopy of the result instead (assuming you really do need mutability).
Why are you trying to prune ahead of time? If you decode the original JSON, you'll be able to extract the level array from the data dict in the decoded dict.
It's not clear what else you're trying to accomplish or why you are going the path you ask about. Note, this doesn't necessarily mean your path is wrong, just that without a clearer indication of what goal you're really trying to accomplish or what you've actually tried (and errored/failed, along with how it failed), you're likely only to get vague/general answers like this.

Read .mobileprovisioning profile with Objective-C

So, I'm trying to open a .mobileprovisioning profile to read what's inside... this is what I'm doing:
NSString *path = [pathURL path];
NSData *data = [[NSFileManager defaultManager] contentsAtPath:path];
Of course I get the data read but I'm not finding the way of getting of get this data into something useful... an NSDictionary, an NSString or whatever...
I've already tried:
NSString *newStr = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
Any idea? I'm sure this is an encoding issue, but I can't solve it after reading and googling for some time... I think the provisioning profile is saved as hexadecimal, but I don't know how to read that from objective-c. I have found this but there wasn't an useful answer.
How to convert NData populated with hex values to NSString
Thanks!
The following method should do what you want. As #rbrockerhoff says the mobile provisioning profile is an encoded CMS message. This method uses a decoder to first decode the data using the CMS functions and then creates the plist string/contents from the decoded data. This string can then be converted into a dictionary which is returned from the method. The dictionary will contain all the details from the mobile provisioning profile.
- (NSDictionary *)provisioningProfileAtPath:(NSString *)path {
CMSDecoderRef decoder = NULL;
CFDataRef dataRef = NULL;
NSString *plistString = nil;
NSDictionary *plist = nil;
#try {
CMSDecoderCreate(&decoder);
NSData *fileData = [NSData dataWithContentsOfFile:path];
CMSDecoderUpdateMessage(decoder, fileData.bytes, fileData.length);
CMSDecoderFinalizeMessage(decoder);
CMSDecoderCopyContent(decoder, &dataRef);
plistString = [[NSString alloc] initWithData:(__bridge NSData *)dataRef encoding:NSUTF8StringEncoding];
NSData *plistData = [plistString dataUsingEncoding:NSUTF8StringEncoding];
plist = [NSPropertyListSerialization propertyListWithData:plistData options:NSPropertyListImmutable format:nil error:nil]
}
#catch (NSException *exception) {
NSLog(#"Could not decode file.\n");
}
#finally {
if (decoder) CFRelease(decoder);
if (dataRef) CFRelease(dataRef);
}
return plist;
}
A .mobileprovisioning file is an encoded CMS message.
See https://developer.apple.com/library/mac/documentation/security/Reference/CryptoMessageRef/Reference/reference.html for details and an API for decoding it.
If you just want the encoded property list as text, a quick-and-dirty hack is to get the byte pointer for your NSData, scan for the beginning "<?xml" and up to the closing "</plist>". Then make a NSString from that.
You can simply force to open the mobile provisioning profile in TextEdit where you can see the
interior contents and in which you can trim/Edit the encoded CMS message or whatever you want . Then you can simply decode with NSData encodewithUTF string method.
Hope this helps.

How to parse multiple json in objective-c?

I am trying to parse JSON in objective-c but am having trouble. The example in the tutorial I am following only goes to the first level after the parent node. I am trying to get data that is a bit deeper. Any advice on how to do this?
The elements I am trying to get:
Title: data.children[i].data.title
Thumbnail: data.children[i].data.thumbnail
Json: http://www.reddit.com/r/HistoryPorn/.json
NSURL *blogURL = [NSURL URLWithString:#"http://www.reddit.com/r/HistoryPorn/.json"];
NSData *jsonData = [NSData dataWithContentsOfURL:blogURL];
NSError * error = nil;
NSDictionary *dataDictionary = [NSJSONSerialization JSONObjectWithData:jsonData options:0 error:&error];
self.blogPosts = [NSMutableArray array];
NSArray * blogPostsArray = [dataDictionary objectForKey:#"data"];
for (NSDictionary *bpDictionary in blogPostsArray) {
BlogPost * blogPost = [BlogPost blogPostWithTitle:[bpDictionary objectForKey:#"title"]];
blogPost.thumbnail = [bpDictionary objectForKey:#"thumbnail"];
blogPost.url = [NSURL URLWithString:[bpDictionary objectForKey:#"url"]];
[self.blogPosts addObject:blogPost];
}
With the new syntax it should be easier to gets keys in a nested dictionaries. You can know the full keys/indexes path by just drawing a tree, remember that a dictionary starts with braces, and an array starts with brackets. For example let's retrieve the "thumbnail" and "url" value for the first entry in the children array:
NSDictionary *json = [NSJSONSerialization JSONObjectWithData:jsonData options:0 error:&error];
if(!json)
{
// Always handle eventual errors:
NSLog(#"%#",error);
return;
}
NSString* thumbnail= json[#"data"][#"children"][0][#"data"][#"thumbnail"];
NSString* url= json[#"data"][#"children"][0][#"data"][#"url"];

NSData writeToFile writes Plist successfully, but then crashes, giving NSInvalidArgumentException

I am converting a JSON file to a plist using the new NSJSONSerialization class and NSPropertyListSerialization class. I manage to convert my JSON to a Plist without errors, but then, at my last step, when I go to write the plist to my desktop, the program crashes, but AFTER the Plist has been generated!
NSData *data = [[NSData alloc] initWithContentsOfURL:path]; \\(NSURL *)path -->goes to my JSON file
NSMutableDictionary *json = [NSJSONSerialization JSONObjectWithData:data
options:NSJSONReadingMutableContainers
error:nil];
//the following removes all key/object pairs where the object is null, because NSPropertyListSerialization with throw an error if there are null values
for (id __strong object in [json objectForKey:#"terms"]) {
if ([object objectForKey:#"image"] == [NSNull null]) {
[object removeObjectForKey:#"image"];
}
}
//the following NSPropertyListSerialization method returns an NSData
id plist = [NSPropertyListSerialization dataFromPropertyList:(id)json
format:NSPropertyListXMLFormat_v1_0
errorDescription:nil];
NSError *writeToFileError;
[plist writeToFile:#"/Users/kalaracey/Desktop/test.plist"
atomically:YES
encoding:NSUTF8StringEncoding
error:&writeToFileError];
Then, at this last line, an NSInvalidArgumentException is thrown, and crashes my program. However, the plist was successfully generated! I can read it, and all is well, except my program crashes.
Could someone please explain why this crashes, and how I could avoid crashing?
The problem seems to be that the variable plist is type id. Cast it to NSData and you should be fine.
NSData *plist = (NSData *) [NSPropertyListSerialization ...];
As you correctly point out in the comment, NSData should use the writeToFile:atomically: method.

Unable to write NSArray to file

I am using google contact data objective c APIs for fetching contacts. I got contacts array from google server now i want to write contact to file. i am using writeToFile:atomically: method for writing array to file but This method is not working for me since i feel that output array from gdata API not contain property list objects. Please suggest any alternate solution.
-(void)fetchData{
GDataServiceGoogleContact *service=[[GDataServiceGoogleContact alloc] init];
[service setShouldCacheResponseData:YES];
[service setServiceShouldFollowNextLinks:YES];
[service setUserCredentialsWithUsername:[mUsername stringValue] password:[mPassword stringValue]];
// GENERATING THE URL
NSURL *feedURL=[GDataServiceGoogleContact contactFeedURLForUserID:kGDataServiceDefaultUser];
GDataQuery *contQuery=[GDataQueryContact contactQueryWithFeedURL:feedURL];
[contQuery setShouldShowDeleted:YES];
[contQuery setMaxResults:2000];
GDataServiceTicket *ticket=[service fetchFeedWithQuery:contQuery delegate:self didFinishSelector:#selector(hasFetchedContacts:feed:error:)];
}
-(void) hasFetchedContacts:(GDataServiceTicket*) ticket feed:(GDataFeedContact*) contacts error:(NSError*) err
{
NSArray *contactList=[contacts entries];
NSLog(#"%d",[list writeToFile:#"/Users/subhranil/Desktop/contactList" atomically:NO]);
}
Wrap it up to NSData with:
NSData *data = [NSKeyedArchiver archivedDataWithRootObject:contactList];
Then save NSData to file with:
[data writeToFile:#"/Users/subhranil/Desktop/contactList" atomically:NO];
You can later restore the data back to NSArray using:
NSData *data = [NSData dataWithContentsOfFile: #"yourFilePath"];
NSArray *array = [NSKeyedUnarchiver unarchiveObjectWithData:data]
Just make sure that objects inside your NSArray conform to NSCoding.
You can use byte array for this purpose and NSData for writing to file.
For saving:
NSData *data=[[NSData alloc] initWithBytes:[contacts entries] length:total];
[data writeToFile:#"path" atomically:YES];
total= The total size of the array in bytes
For retrieving:
NSData *newdata = [NSData dataWithContentsOfFile:#"path"];
NSUInteger len = [newdata length];
Byte *byteData = (Byte*)malloc(len);
memcpy(byteData, [newdata bytes], len);
byteData will now contain an array of GDataEntryContact objects and you can use them accordingly.
You can encode/decode GDataObject using an xml as generator.
Encode:
[entry setNamespaces:[entry completeNamespaces]];
NSString *xml = [[entry XMLElement] XMLString];
if (nil != xml)
{
//Store your xml NSString to a file
}
Decode:
NSString *xml = //Read your XML String from file;
NSXMLElement *xmlElement = [[NSXMLElement alloc] initWithXMLString:xml error: &error];
if (!error) {
return [[GDataEntryDocBase alloc] initWithXMLElement:xmlElement parent: nil];
}