KissXML icon indicating copy to clipboard operation
KissXML copied to clipboard

nodesForXPath fails if xml document has DOCTYPE

Open kgrigsby59 opened this issue 13 years ago • 1 comments

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;
        }
    }

kgrigsby59 avatar Aug 01 '12 19:08 kgrigsby59

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.

gamecentric avatar Aug 24 '12 11:08 gamecentric