(Kiss)XML xpath and default namespace

2019-07-03 16:02发布

问题:

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?

回答1:

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


回答2:

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.

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.