Search code examples
iosobjective-cxmlxpathkissxml

(Kiss)XML xpath and default namespace


I am working on an iPhone project that needs to parse some xml. The xml may or may not include a default namespace. I need to know how to parse the xml in case it uses a default namespace. As I need to both read an write xml, I'm leaning towards using KissXML, but I'm open for suggestions.

This is my code:

NSString *content = [NSString stringWithContentsOfFile:[[NSBundle mainBundle]
    pathForResource:@"bookstore" ofType:@"xml"] encoding:NSUTF8StringEncoding error:nil];

DDXMLDocument *theDocument = [[DDXMLDocument alloc] initWithXMLString:content options:0 error:nil];

NSArray *results = [theDocument nodesForXPath:@"//book" error:nil];
NSLog(@"%d", [results count]);

It works as expected on this xml:

<?xml version="1.0" encoding="UTF-8"?>
<bookstore>
<book category="COOKING">
  <title lang="en">Everyday Italian</title>
</book>
<book category="CHILDREN">
  <title lang="en">Harry Potter</title>
</book>
</bookstore>

But when the xml has a namespace, like this, it stops working:

<?xml version="1.0" encoding="UTF-8"?>
<bookstore xmlns="[INSERT RANDOM NAMESPACE]">
<book category="COOKING">
  <title lang="en">Everyday Italian</title>
</book>
<book category="CHILDREN">
  <title lang="en">Harry Potter</title>
</book>
</bookstore>

Of course, I could just preprocess the string and remove the xmlns, though that feels like a sort of ugly hack. What is the proper way to handle this?


Solution

  • The Clean Way: Querying for the Namespace

    You can use two XPath queries, one to fetch the namespace, then register it; as second query use the one you already have including namespaces. I can only help you with the query, but it seems you're quite familiar with namespaces and how to register them in the KissXML framework:

    namespace-uri(/*)
    

    This expression fetches all child nodes starting at the document root, which is per XML definition a single root element, and returns it's namespace uri.

    The Ugly Way: Only Testing for Local Name

    It seems KissXML only supports XPath 1.0. With this less-capable language version, you need to use wildcard selectors at each axis step and compare the local name (without namespace prefix) inside the predicate:

    //*[local-name(.) = 'book']
    

    Starting from XPath 2.0, you could query using the namespace wildcard, which is much shorter:

    //*:book