9

I'm looking for a short example/tutorial on how to read, modify one value and write an xml file using cocoa. Everything that I found is either to simple (just read or write) or to complex (being a full xml editor).

This seems like a pretty standard usage scenario, so I'm hoping that there is something out there...

Community
  • 1
  • 1
hanno
  • 6,401
  • 8
  • 48
  • 80
  • possible duplicate of [Cocoa/Obj-C simple XML file reader - need help](http://stackoverflow.com/questions/5274513/cocoa-obj-c-simple-xml-file-reader-need-help) – jscs May 15 '11 at 20:08
  • 1
    Among others: [Extracting info from XML](http://stackoverflow.com/questions/3739854/) | [Parsing XML](http://stackoverflow.com/questions/1089737/parsing-xml-in-cocoa) | [Best practice to parse XML](http://stackoverflow.com/questions/2237757/) | [Read/Write XML](http://stackoverflow.com/questions/1072979/) – jscs May 15 '11 at 20:11
  • 1
    Thanks, but I'm looking for an example that shows how to read, modify __and__ write an xml tree. The 'duplicates' don't do that. Suppose I have (among other things) a simple counter in an xml file: 1. How do I parse, increment and write it back to the file? – hanno May 15 '11 at 20:29
  • [NSXMLDocument](http://developer.apple.com/library/mac/#documentation/Cocoa/Reference/Foundation/Classes/NSXMLDocument_Class/Reference/Reference.html)? [Sample Code](http://developer.apple.com/library/mac/#samplecode/XMLBrowser/Introduction/Intro.html%23//apple_ref/doc/uid/DTS40008875) – jscs May 15 '11 at 20:37

3 Answers3

6

I was in error, when I claimed that writing such elements couldn't be done. Apple have two XML parsers, one for NSDictionary/NSArray/NSString/NSNumber/etc. objects and one called NSXMLDocument.

I've removed the previous code, which was Mac OS X 10.3 compatible; the code mentioned below will allow you to have xml files containing whatever tags and attributes you like. In my example, the code will create an XML file that looks like the following:

<root><counter>1</counter></root>

Note: You can even remove the 'counter' and rename 'root' to 'counter', if you want to reduce it further.

The new code is compatible with Mac OS X 10.4 and forward; it's tested and works out-of-the-box:

- (void)incrementCounter
{
    NSXMLDocument   *xmlDoc;
    NSError         *error;
    NSURL           *url;
    NSXMLElement    *root;
    id              item;
    NSData          *data;
    NSArray         *children;
    int             counter;
    NSString        *pathname;

    pathname = [@"~/myFile" stringByExpandingTildeInPath];
    url = [NSURL fileURLWithPath:pathname];

    xmlDoc = [[NSXMLDocument alloc] initWithContentsOfURL:url options:NSXMLDocumentTidyXML error:&error];
    if(xmlDoc)
    {
        root = [xmlDoc rootElement];
    }
    else
    {
        root = [NSXMLNode elementWithName:@"root"];
        xmlDoc = [[NSXMLDocument alloc] initWithRootElement:root];
    }

    // fetch value:
    children = [root nodesForXPath:@"counter" error:&error];
    item = [children count] ? [children objectAtIndex:0] : NULL;

    // modify value:
    counter = item ? [[item stringValue] intValue] : 0;
    counter++;
    if(NULL == item)
    {
        item = [NSXMLNode elementWithName:@"counter" stringValue:@"0"];
        [root insertChild:item atIndex:0];
    }
    [item setStringValue:[[NSNumber numberWithInt:counter] stringValue]];

    // write:
    data = [xmlDoc XMLData];
    [data writeToURL:url atomically:YES];
}
  • This answer doesn't make any sense to me. – hanno Feb 16 '13 at 15:15
  • I've now rewritten my answer. If you need to support Mac OS X below 10.4, I'll have to rewrite my answer again. It all depends on if you have the NSXMLDocument class available. –  Feb 17 '13 at 18:00
  • Did you get it working with the new code above or are you still having trouble ? ... Try invoking [self incrementCounter]; from -awakeFromNib in a completely new project; it should generate 'myFile' in your home directory and increment the counter each time you run the program. –  Feb 21 '13 at 19:11
2

Here is my solution to the problem - only the part where I write out the file which was previously read and parsed...

Using the solution of PacMan-- I encountered the problem that the output xml file wasn't formatted nicely (no line breaks at all).

The other point is, I usally prefer using XPath navigating through a XML document.

My code is part of a class, so I have a property for each node (NSString* cfaLayout and NSInteger bitsPerPixel):

-(BOOL) savePropertiesFile{
BOOL isSuccess=FALSE;
NSError* error;
NSXMLElement* currentElement;
NSArray* nodes;

/* parse existing url file before saving it to new url */
if([_documentUrl checkResourceIsReachableAndReturnError:&error]==YES){
    self.document=[[NSXMLDocument alloc]
                                 initWithContentsOfURL:_documentUrl
                                 options:0
                                 error:&error];

    /* check top node */
    nodes=[_document nodesForXPath:@"/ImageFormat-Properties"
                                                     error:&error];
    if([nodes count]==0){
        return FALSE;
    }

    /* cfa layout */
    nodes=[_document nodesForXPath:@"/ImageFormat-Properties/ImageFormatDetails/CFALayout[text()]"
                                                     error:&error];
    if([nodes count]>0){
        currentElement=[nodes objectAtIndex:0];
        [currentElement setStringValue:[_cfaLayout lowercaseString]];
    }
    else{
        return FALSE;
    }

    /* bitsPerPixel */
    nodes=[_document nodesForXPath:@"/ImageFormat-Properties/ImageFormatDetails/BitsPerPixel[text()]"
                                                     error:&error];
    if([nodes count]>0){
        currentElement=[nodes objectAtIndex:0];
        [currentElement setStringValue:[NSString stringWithFormat: @"%ld", (long)_bitsPerPixel]];
    }
    else{
        return FALSE;
    }
}
[_document setDocumentContentKind:NSXMLDocumentXMLKind];
[_document setCharacterEncoding:@"UTF-8"];
[_document validateAndReturnError:&error];
if(error){
    return FALSE;
}

[self setDocumentUrl:_documentSaveAsUrl];
NSData* xmlData=[_document XMLDataWithOptions:NSXMLNodePrettyPrint];
isSuccess=[xmlData writeToURL:_documentSaveAsUrl atomically:YES];

return isSuccess;

}

Maybe useful for someone...

Johann Horvat
  • 1,285
  • 1
  • 14
  • 18
  • the output xml file wasn't formatted nicely (no line breaks at all): try"NSData *data = [xmlDoc XMLDataWithOptions:NSXMLNodePrettyPrint];" – geowar Aug 21 '21 at 01:12
-4

If you are not familiar with xpaths, then I suggest you read up on them, xpaths are a way to find nodes in the xml tree.

Nathan Day
  • 113
  • 1
  • 6
    That's No answer, son.. He's asking a specific question... and advising him to "read up" on xpaths is just rude. I agree with the OP... Personally, I can find a MILLION ways to read XML in cocoa, but for the life of me _can't_ find a _single, readily apparent_ way to turn an NSArray, or and NSDictionary, etc. in an XML "Object" - for the life of me! – Alex Gray Nov 21 '11 at 16:11