Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

nodesForXPath fails if xml document has DOCTYPE #20

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

nodesForXPath fails if xml document has DOCTYPE #20

kgrigsby59 opened this issue Aug 1, 2012 · 1 comment

Comments

@kgrigsby59
Copy link

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;
        }
    }
@gamecentric
Copy link

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 pushed a commit to tipbit/KissXML that referenced this issue Apr 28, 2013
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 robbiehanson#19, the second half of issue robbiehanson#20.
ewanmellor pushed a commit to tipbit/KissXML that referenced this issue Apr 28, 2013
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 robbiehanson#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).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants