rmirabelle
rmirabelle

Reputation: 6446

PHP xpath fails to locate node

Given the following fragment:

<svrl:failed-assert>
    <svrl:text>message</svrl:text>
        <svrl:diagnostic>
            <diag xmlns="example2.com">
                <record>
                    <record.01>141</record.01>
                </record>
            </diag>
        </svrl:diagnostic>
</svrl:failed-assert>

I'm able to locate all the failed-assert nodes with xpath:

$xml = simplexml_load_file($my_path);
$xml->registerXPathNamespace('svrl', 'example.com');
$errors = $xml->xpath('//svrl:failed-assert');

Then I loop through each and attempt to find children:

foreach($errors as $error) {
     $text = $error->xpath('svrl:text'); //works fine
     $record = $error->xpath('svrl:diagnostic/diag/record'); //FAILS
}

No matter what I try, in what combination, I cannot get the xpath expression to properly locate the diagor record nodes.

I assume this may have something to do with the fact that the child nodes are not namespaced, while the parent nodes are. I have no control over the source document namespacing.

Is it possible to locate the node(s) in question with an xpath expression?

Upvotes: 1

Views: 288

Answers (4)

har07
har07

Reputation: 89285

That's because diag element has default namespace declared which URI is "example2.com". Notice that descendant elements without prefix inherit ancestor's default namespace implicitly.

You need to map a prefix to the default namespace, and use the prefix to reference elements in that namespace :

foreach($errors as $error) 
{
     $error->registerXPathNamespace('foo', 'example2.com');
     $text = $error->xpath('svrl:text'); 
     $record = $error->xpath('svrl:diagnostic/foo:diag/foo:record');
}

Upvotes: 1

cpugourou
cpugourou

Reputation: 781

Dealing with xpath is one of the biggest pain with XML. Beside beeing a pain, it is also an extremely slow processing.(100 times slower than arrays).

After fighting xpath for years, I have decided just to not use it anymore and I convert XML to Json to work on fast...

Depends on what you need , but you might want to consider this option.

Usage:

$var = '<svrl:failed-assert>
<svrl:text>message</svrl:text>
    <svrl:diagnostic>
        <diag xmlns="example2.com">
            <record>
                <record.01>141</record.01>
            </record>
        </diag>
    </svrl:diagnostic>
</svrl:failed-assert>';

$result=xmlstr_to_array($var);

will output:

array(2) { ["svrl:text"]=> string(7) "message" ["svrl:diagnostic"]=> array(1) { ["diag"]=> array(1) { ["record"]=> array(1) { ["record.01"]=> string(3) "141" } } } }

functions:

// load xml string
function xmlstr_to_array($xmlstr) {
    $doc = new DOMDocument();
    $doc->loadXML($xmlstr);
    return domnode_to_array($doc->documentElement);
}

// convert nodes
function domnode_to_array($node) {
$output = array();
switch ($node->nodeType) {
    case XML_CDATA_SECTION_NODE:
    case XML_TEXT_NODE:
        $output = trim($node->textContent);
        break;
    case XML_ELEMENT_NODE:
        for ($i = 0, $m = $node->childNodes->length; $i < $m; $i++) {
            $child = $node->childNodes->item($i);
            $v = domnode_to_array($child);
            if (isset($child->tagName)) {
                $t = ConvertTypes($child->tagName);
                if (!isset($output[$t])) {
                    $output[$t] = array();
                }
                $output[$t][] = $v;
            } elseif ($v) {
                $output = (string) $v;
            }
        }
        if (is_array($output)) {
            if ($node->attributes->length) {
                $a = array();
                foreach ($node->attributes as $attrName => $attrNode) {
                    $a[$attrName] = ConvertTypes($attrNode->value);
                }
                $output['@attributes'] = $a;
            }
            foreach ($output as $t => $v) {
                if (is_array($v) && count($v) == 1 && $t != '@attributes') {
                    $output[$t] = $v[0];
                }
            }
        }
        break;
}
return $output;
}

//convert types
function ConvertTypes($org) {
if (is_numeric($org)) {
    $val = floatval($org);
} else {
    if ($org === 'true') {
        $val = true;
    } else if ($org === 'false') {
        $val = false;
    } else {
        if ($org === '') {
            $val = null;
        } else {
            $val = $org;
        }
    }
}
return $val;
}

Upvotes: 0

SomeDude
SomeDude

Reputation: 14228

By using this :

//*[namespace-uri()='example2.com']/*[name()='record.01']/text()

you get 141

Upvotes: 0

reieRMeister
reieRMeister

Reputation: 121

You could try an XPath expession like

svrl:diagnostic/*[namespace-uri()='example2.com' and local-name()='diag']/*[namespace-uri()='example2.com' and local-name()='record']

Isn’t very beautiful of course but should do the job.

Upvotes: 0

Related Questions