how to extract data from cocoa iPhone sax xml parsing routine

I'm trying to read in and parse an xml document in an iPhone app. I begin parsing and then use the override method:

   static void startElementSAX(void *ctx, const xmlChar *localname, const xmlChar *prefix, const xmlChar *URI, 
                        int nb_namespaces, const xmlChar **namespaces, int nb_attributes, int nb_defaulted, const xmlChar **attributes)

I then try to convert the attributes to a string with:

   NSString *str1 = [[NSString alloc] initWithCString:attributes encoding:NSUTF8StringEncoding];

Why does the attributes parameter have two ** in front of it. And why when trying to extract the data and convert it to a string with the above code do I get the warning:

passing argument 1 of 'initWithCString:encoding:' from incompatible pointer type.

Answers


The documentation for libxml's start element callback states that the pointer is to an array that hold 5 values for each attribute (the number of attributes is returned in nb_attributes). This means that every 5th value in the array is a new attribute item.

The five items for each attribute are:

  1. localname (the name of the attribute)
  2. prefix (the namespace of the attribute)
  3. URI
  4. [start of] value (a pointer to the start of the xmlChar string for the value)
  5. end [of value] (a pointer to the end of the xmlChar string for the value)

So you need to step through the array, get each value out of the items for the first attribute, then use the start value pointer to get the xmlChar string that is length = end - start. Then start over with the next attribute till you read in nb_attributes worth.

If that makes your head ache then I strongly suggest you switch to Apple's NSXMLParser (link may require login, or use this link NSXMLParser). In which case you would get the attributes as an NSDictionary. To get all the attributes out of it you could do the following:

for (NSString *attributeName in [attributeDict allKeys]) {
    NSString *attributeValue = [attributeDict objectForKey:attributeName];
    // do something here with attributeName and attributeValue
}

If you have access to the iPhone developer site then look at the example SeismicXML.


The sample is great except for two things:

  • you need to bump 'i' by 5 after each loop since there are 5 items for each attribute.
  • doing strlen() on both begin and end is expensive; it's easier to simply subtract begin from end
for (int i = 0; i < nb_attributes*5; i += 5) 
{
    const char *attr = (const char *)attributes[i];
    const char *begin = (const char *)attributes[i + 3];
    const char *end = (const char *)attributes[i + 4];
    int vlen = end - begin;
    char val[vlen + 1];
    strncpy(val, begin, vlen);
    val[vlen] = '\0';
    NSLog(@"attribute %s = '%s'", attr, val);
}

The accepted answers explanation is correct, but it's helpful to view some example code too. Here is just one way to extract the value from the attributes, at least it works when I tested it. I'm far from being a C guru though.

for (int i = 0; i < nb_attributes; i += 5) {
    const char *attr = (const char *)attributes[i];
    const char *begin = (const char *)attributes[i + 3];
    const char *end = (const char *)attributes[i + 4];
    int vlen = strlen(begin) - strlen(end);
    char val[vlen + 1];
    strncpy(val, begin, vlen);
    val[vlen] = '\0';
    NSLog(@"attribute %s: %d = %s", attr, i, val);
}

NSXMLParser is nice, but from what I can tell, it downloads the entire XML before processing. Using libxml it can read in chunks at a time. It allows greater flexibility, but higher learning curve.


The '**' notation means "pointer to a pointer." In C/C++, a "string" is represented by an array of characters. An array is actually just a pointer under the covers, so a string in C/C++ can actually be declared as either "char[]" or "char*". The [] notation compiles down to a pointer to an array.

A common example of this is the typical "main" function in C/C++:

int main(int argc, char **argv)

Which is equivalent to:

int main(int argc, char *argv[])

argv is an array of char* "strings" (the command-line arguments to the program).

I can't provide an example at the moment, but it looks like you need to iterate over attributes to access the individual strings. For example, attributes[0] would be the first attribute string (an xmlChar*). You should be able to convert each individual attribute to an NSString.


const xmlChar **namespaces is an array of CStrings (int nb_namespaces tells you how many). If you want each namespace as an NSString, you could do something like the following:

NSMutableArray *namespaces = [[NSMutableArray alloc] init];

int i;
for (i = 0; i < nb_namespaces; i++) {
    NSString *namespace = [[NSString alloc] initWithCString:attributes[i] encoding:NSUTF8StringEncoding];
    [namespaces addObject:namespace];
}

The initWithCString method is expecting xmlChar *, which is a pointer to an xmlChar (the first char in a CString).

xmlChar ** means pointer to a pointer to an xmlChar (the first char in the first CString).


Need Your Help

How do I make a dependent dropdown select menu choice show/hide another dropdown select menu using Javascript?

javascript jquery html drop-down-menu

Sorry about the confusing title, but basically my question is how do I use the selection from one dropdown menu to decide which dropdown menu shows up next? I want to do something like this:Select

How to stop UINavigationBar title from animating while push transition

ios objective-c xcode uinavigationbar navigationbar

Is there any way to stop the titleView on UINavigationBar to animate when I push/pop view controllers. TitleView for each screen is same (app's logo).