Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

(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?

like image 301
Reason Avatar asked Dec 16 '22 09:12

Reason


2 Answers

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
like image 184
Jens Erat Avatar answered Jan 01 '23 10:01

Jens Erat


According to this comment KissXML implements "correct" behaviour while NSXML doesn't. Which doesn't exactly help. There is a proposed fix for this waiting to be merged. [edit] 11/2021 - still waiting to be merged!

Expanding on the accepted answer's first proposed solution the workaround I found was to rename the default namespace and then use that prefix in my XPath queries. Something like:

    DDXMLNode *defaultNamespace = [document.rootElement namespaceForPrefix:@""];
    defaultNamespace.name = @"default";
    NSArray *xmlNodes = [[document rootElement] nodesForXPath:@"//default:foo/default:bar" error:nil];

This seems cleaner to me than textual processing of the file. You could of course check and handle namespace collisions but the above should work in most simple cases.

like image 20
Robin Macharg Avatar answered Jan 01 '23 11:01

Robin Macharg