Skip to content

nodesForXPath fails if xml document has DOCTYPE #20

Open
kgrigsby59 opened this Issue Aug 1, 2012 · 1 comment

2 participants

@kgrigsby59

If you add a DOCTYPE to the xml in testNodesForXPath you'll see that test 6 fails. For instance I added the following.

[xmlStr appendString:@"<?xml version=\"1.0\"?>"];
[xmlStr appendString:@"<!DOCTYPE AirSync PUBLIC \"-//AIRSYNC//DTD AirSync//EN\" \"http://www.microsoft.com/\">"];
[xmlStr appendString:@"<menu xmlns:a=\"tap\">"];

It fails because in DDXMLNode nodesForXPath:error: because the line

xmlNodePtr rootNode = (doc)->children;

returns the a node with the name AirSync instead of the real root node. I changed this to

    xmlNodePtr rootNode = xmlDocGetRootElement(doc);

Also if the xml uses a default namespace such as

<Sync xmlns="http://synce.org/formats/airsync_wm5/airsync">

and you try to add a prefixed namespace to the root node such as

<Sync xmlns=as="http://synce.org/formats/airsync_wm5/airsync">

it will fail. This is because a non-prefixed namespace is added in the line

    xmlXPathRegisterNs(xpathCtx, ns->prefix, ns->href);

I changed this to

            if (ns->prefix && ns->prefix[0] != '\0') {
                xmlXPathRegisterNs(xpathCtx, ns->prefix, ns->href);
            }

and now it works. In summary I changed

    xpathCtx = xmlXPathNewContext(doc);
    xpathCtx->node = (xmlNodePtr)genericPtr;

    xmlNodePtr rootNode = (doc)->children;
    if(rootNode != NULL)
    {
        xmlNsPtr ns = rootNode->nsDef;
        while(ns != NULL)
        {
            xmlXPathRegisterNs(xpathCtx, ns->prefix, ns->href);

            ns = ns->next;
        }
    }

to

    xpathCtx = xmlXPathNewContext(doc);
    xpathCtx->node = (xmlNodePtr)genericPtr;

    xmlNodePtr rootNode = xmlDocGetRootElement(doc);
    if(rootNode != NULL)
    {
        xmlNsPtr ns = rootNode->nsDef;
        while(ns != NULL)
        {
            if (ns->prefix && ns->prefix[0] != '\0') {
                xmlXPathRegisterNs(xpathCtx, ns->prefix, ns->href);
            }

            ns = ns->next;
        }
    }
@iaanus
iaanus commented Aug 24, 2012

Thanks for the fixes, I was getting mad at this. However, there is still a major problem with default namespaces. In other words, if you add a default namespace on the root node, as in:

[xmlStr appendString:@"<menu xmlns=\"restaurant\" xmlns:a=\"tap\">"];

then test 4 fails, both with your fix and without. Presumably, all other tests after test 4 should also fail. This is really annoying. According to this post http://www.perlmonks.org/?node_id=530519 the behaviour of KissXML seems to actually be the correct one, while NSXML is wrong. The problem boils down to the fact that, according to post above, "/menu/pizza" shall match both "menu" and "pizza" in the null namespace, not in the default one.

However, since:

1) one of the design goals of KissXML is to be a replacemente with NSXML

2) the current behaviour makes XPath useless whenever you have a default namespace (there's no way to match an element in the default namespace, since the XPath syntax doesn't allow you to specify a namespace that doesn't have a prefix)

I hope this could be fixed somehow.

Waiting for a better fix, I am now using this one instead of yours:

xmlNodePtr rootNode = xmlDocGetRootElement(doc);
if(rootNode != NULL)
{
    xmlNsPtr ns = rootNode->nsDef;
    while(ns != NULL)
    {
        const xmlChar* prefix = ns->prefix;
        if (!prefix || !*prefix)
        {
            prefix = rootNode->name;
        }
        xmlXPathRegisterNs(xpathCtx, prefix, ns->href);

        ns = ns->next;
    }
}

with this, at least there's a way to match elements in the default namespace (for example you can write "/restaurant:menu/restaurant:pizza" instead of "/menu/pizza"). Ugly, but better than nothing.

@ewanmellor ewanmellor added a commit to tipbit/KissXML that referenced this issue Apr 28, 2013
@ewanmellor ewanmellor Implement DDXMLNode nodesForXPath:namespaceMappings:error.
This allows the caller to pass in an NSDictionary specifying the mapping
from namespace prefix to namespace URL.  Namespace prefixes specified in
the given XPath will then be resolved against that mapping.

This allows callers to find nodes using default namespaces, e.g.
<element xmlns='<schema URL>'>...</element>.  The caller must specify
prefix=<schema URL> in namespaceMappings, and then can use prefix:element
in the XPath.

If namespaceMappings is nil, then the existing behavior is used
(parsing the namespaces from the document node).  This works in the
situation where the namespaces are all named explicitly, and all on the
root node, but not in general.

This addresses upstream issue #19, the second half of issue #20.
48701f1
@ewanmellor ewanmellor added a commit to tipbit/KissXML that referenced this issue Apr 28, 2013
@ewanmellor ewanmellor Use xmlDocGetRootElement when setting up namespaces.
The existing code uses doc->children, which is wrong in the case that the
document has a doctype or PI nodes at the start.

This fix is originally from upstream issue #20, by kgrigsby59.

This changeset re-applies the fix on top of the
nodesforxpath-namespacemappings branch (the two changes are in the same
area of code).
f3482cf
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Something went wrong with that request. Please try again.