Reputation: 6446
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 diag
or 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
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
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
Reputation: 14228
By using this :
//*[namespace-uri()='example2.com']/*[name()='record.01']/text()
you get 141
Upvotes: 0
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