Given the following xml:
<data xmlns:ns2="...">
<versions>
<ns2:version type="HW">E</ns2:version>
<ns2:version type="FW">3160</ns2:version>
<ns2:version type="SW">3.4.1 (777)</ns2:version>
</versions>
...
</data>
I am trying to parse the third attribute ~ns2:version type="SW" but when running the following code I get nothing..
$s = simplexml_load_file('data.xml');
echo $s->versions[2]->{'ns2:version'};
Running this gives the following output:
$s = simplexml_load_file('data.xml');
var_dump($s->versions);
How can I properly get that attribute?
You've got some quite annoying XML to work with there, at least as far as SimpleXML is concerned.
Your version elements are in the ns2 namespace, so in order to loop over them, you need to do something like this:
$s = simplexml_load_string($xml);
foreach ($s->versions[0]->children('ns2', true)->version as $child) {
...
}
The children() method returns all children of the current tag, but only in the default namespace. If you want to access elements in other namespaces, you can pass the local alias and the second argument true.
The more complicated part is that the type attributes is not considered to be part of this same namespace. This means you can't use the standard $element['attribute'] form to access it, since your element and attribute are in different namespaces.
Fortunately, SimpleXML's attributes() method works in the same way as children(), and so to access the attributes in the global namespace, you can pass it an empty string:
$element->attributes('')->type
In full, this is:
$s = simplexml_load_string($xml);
foreach ($s->versions[0]->children('ns2', true)->version as $child) {
echo (string) $child->attributes()->type, PHP_EOL;
}
This will get you the output
HW
FW
SW
To get the third attribute.
$s = simplexml_load_file('data.xml');
$sxe = new SimpleXMLElement($s);
foreach ($sxe as $out_ns) {
$ns = $out_ns->getNamespaces(true);
$child = $out_ns->children($ns['ns2']);
}
echo $child[2];
Out put:
3.4.1 (777)
Related
How can one enforce simplexml_load_string( ) to use same data structure at each node point.
$xml = "
<level1>
<level2>
<level3>Hello</level3>
<level3>stackoverflow</level3>
</level2>
<level2>
<level3>My problem</level3>
</level2>
</level1>";
$xmlObj = simplexml_load_string($xml)
var_dump($xmlObj);
Examining the output,
level1 is an object; level2 is an array; level2[0] is an array.
level2[1] is an object, because there's only one child node, which I'll rather have as a single index array.
I'm collecting the xml from user, and there may be 1 or more nodes inside each level2. My sanitisation block is a foreach loop which fails when there's only one node inside level2.
The sanitation block looks something like this
foreach($xmlObj -> level2 as $lvl2){
if($lvl2 -> level3[0] == 'condition'){ doSomething( ); }
}
doSomething() works fine when <level2> always has more than one child node in the xml string. If <level2> has only one child <level3> node, an error about trying to get attribute of a non-object comes up.
var_dump shows that the data type changes from object to array depending on how many nodes are nested within.
I'll prefer a way to ensure <level2> to always be an array regardless of how many children are within. That saves me from editing too much. But any other way out would suffice.
Thanks
It is not an information available in the XML itself. So you will have to add it in your implementation. SimpleXML provides both list and item access to a child elements. If you access it as a list (for example with foreach) it will provide all matching child elements.
$xml = "
<level1>
<level2>
<level3>Hello</level3>
<level3>stackoverflow</level3>
</level2>
<level2>
<level3>My problem</level3>
</level2>
</level1>";
$level1 = new SimpleXMLElement($xml);
$result = [];
foreach($level1->level2 as $level2) {
$data2 = [];
foreach ($level2->level3 as $level3) {
$data2[] = (string)$level3;
}
$result[] = $data2;
}
var_dump($result);
So the trick is to use the SimpleXMLElement instance directly and not convert it into an array. Do not treat the creation of your JSON structure as a generic conversion. Build up a specific output while reading the XML using SimpleXML.
I am trying process data retrieved with SimpleXML and am having great difficulty. I have read numerous threads here about this subject, they all LOOK like what I am doing, but mine are not working. Here's what I've got:
<ROOT>
<ROWS COMP_ID="165462">
<ROWS COMP_ID="165463">
</ROOT>
My code:
$xml = simplexml_load_file('10.xml');
foreach( $xml->ROWS as $comp_row ) {
$id = $comp_row->COMP_ID;
}
As I step through this in my debugger, I can see that $id is not set to the string value of COMP_ID, but becomes a SimpleXMLElement itself containing the CLASSNAME object. I've tried many variations of addressing this attribute but none work, including $comp_row->attributes()->COMP_ID and others.
What am I missing?
SimpleXML is an array-like object. Cheat sheet:
Unprefixed child elements as numeric-index or traversable
Does not include prefixed elements (NOTE, I really mean prefixed, not null-namespace! SimpleXMLElement handling of namespaces is a strange and arguably broken.)
first child: $sxe[0]
new SimpleXMLElement with a subset of matching elements: $sxe->ROWS, $sxe->{'ROWS'}
iterate children: foreach ($sxe as $e), $sxe->children()
Text content: (string) $sxe. SimpleXMLElement always returns another SimpleXMLElement, so if you need a string cast it explicitly!
Prefixed child elements:
$sxe->children('http://example.org') returns a new SimpleXMLElement with elements
in the matching namespace, with namespace stripped so you can use it like the previous section.
Attributes in null namespace as key-index:
specific attribute: `$sxe['attribute-name']
all attributes: $sxe->attributes()
$sxe->attributes() returns a special SimpleXMLElement that shows attributes as both child elements and attributes, so both the following work:
$sxe->attributes()->COMP_ID
$a = $sxe->attributes(); $a['COMP_ID'];
Value of an attribute: coerce to string (string) $sxe['attr-name']
Attributes in other namespaces:
all attributes: $sxe->attributes('http://example.org')
specific attribute: $sxe_attrs = $sxe->attributes('http://example.org'); $sxe_attrs['attr-name-without-prefix']
What you want is:
$xml = '<ROOT><ROWS COMP_ID="165462"/><ROWS COMP_ID="165463"/></ROOT>';
$sxe = simplexml_load_string($xml);
foreach($sxe->ROWS as $row) {
$id = (string) $row['COMP_ID'];
}
You're missing...
foreach( $xml->ROWS as $comp_row ) {
foreach ($comp_row->attributes() as $attKey => $attValue) {
// i.e., on first iteration: $attKey = 'COMP_ID', $attValue = '165462'
}
}
PHP Manual: SimpleXMLElement::attributes
I really need help with using namespaces. How do I get the following code to work properly?
<?php
$mytv = simplexml_load_string(
'<?xml version="1.0" encoding="utf-8"?>
<mytv>
<mytv:channelone>
<mytv:description>comedy that makes you laugh</mytv:description>
</mytv:channelone>
</mytv>'
);
foreach ($mytv as $mytv1)
{
echo 'description: ', $mytv1->children('mytv', true)->channelone->description;
}
?>
All I'm trying to do is get the content inside the name element.
when ever yu are using the namespaces in xml yu should define the namespaces what ever you use..! in the code what i posted you can see how you can define the namespace you are using..
you need to display the description specific to the namespace isn't it..? correct me if I'm wrong., and please post yur purpose properly so that i can understand your problem..
Use this code and see if you can get some idea..
$xml ='<mytv>
<mytv:channelone xmlns:mytv="http://mycompany/namespaces/mytvs">
<mytv:description >comedy that makes you laugh</mytv:description>
</mytv:channelone>
</mytv>';
$xml = simplexml_load_string($xml);
$doc = new DOMDocument();
$str = $xml->asXML();
$doc->loadXML($str);
$bar_count = $doc->getElementsByTagName("description");
foreach ($bar_count as $node)
{
echo $node->nodeName." - ".$node->nodeValue."-".$node->prefix. "<br>";
}
here., the value, "$node->prefix" will be the namespace of the tag containing "description".
getElementsByTagName("description") is used to get all the elements in the xml containing description as tags...!! and then later using the "$node->prefix" you compare with the specific namespace as required for you and then print..
I'm trying to get the contents of the XML:
$xmlstr = "<?xml version=\"1.0\" ?>
<article>
<Art>
<test>Hello</test>
</Art>
<Another>
<g>gooo</g>
</Another>
</article>";
$dom =domxml_open_mem($xmlstr);
$calcX = &$dom->xpath_new_context();
$cnt = $calcX->xpath_eval($querystring);
foreach ($cnt->nodeset as $node)
{
print_r($node);
}
Is there a way that I can get the content when the querystring is //article/Art?
What I'm looking for is:
<test>Hello</test>
If I use $node->get_content(), then the result is Hello.
I'm working with PHP4, so I'm unable to use SimpleXML [which is PHP5]. $node is DOMElement.
$node->nodeValue causes:
Notice: Undefined property: nodeValue in .php on line 19
Is there a way that I can get the
content when the querystring is
//article/Art?
What I'm looking for is:
<test>Hello</test>
Use:
/article/Art/*
This means: Select all elements that are children of an Art element that is a child of the top element named article .
If you want all nodes below /article/Art, use:
/article/Art/node()
This selects all elements, text-nodes, comment nodes and processing-instruction nodes that are children of the top element named article .
It's been awhile that I used the old PHP4 DOM extension, but try
DomNode->dump_node - Dumps a single node
Example:
foreach ($cnt->nodeset as $node) {
echo $node->dump_node;
}
If the above doesn't do what you are looking for, try the other dump_* methods in DomDocument.
I have an XML document that looks like this:
<Data
xmlns="http://www.domain.com/schema/data"
xmlns:dmd="http://www.domain.com/schema/data-metadata"
>
<Something>...</Something>
</Data>
I am parsing the information using SimpleXML in PHP. I am dealing with arrays and I seem to be having a problem with the namespace.
My question is: How do I remove those namespaces? I read the data from an XML file.
Thank you!
I found the answer above to be helpful, but it didn't quite work for me.
This ended up working better:
// Gets rid of all namespace definitions
$xml_string = preg_replace('/xmlns[^=]*="[^"]*"/i', '', $xml_string);
// Gets rid of all namespace references
$xml_string = preg_replace('/[a-zA-Z]+:([a-zA-Z]+[=>])/', '$1', $xml_string);
If you're using XPath then it's a limitation with XPath and not PHP look at this explanation on xpath and default namespaces for more info.
More specifically its the xmlns="" attribute in the root node which is causing the problem. This means that you'll need to register the namespace then use a QName thereafter to refer to elements.
$feed = simplexml_load_file('http://www.sitepoint.com/recent.rdf');
$feed->registerXPathNamespace("a", "http://www.domain.com/schema/data");
$result = $feed->xpath("a:Data/a:Something/...");
Important: The URI used in the registerXPathNamespace call must be identical to the one that is used in the actual XML file.
The following PHP code automatically detects the default namespace specified in the XML file under the alias "default". No all xpath queries have to be updated to include the prefix default:
So if you want to read XML files rather they contain an default NS definition or they don't and you want to query all Something elements, you could use the following code:
$xml = simplexml_load_file($name);
$namespaces = $xml->getDocNamespaces();
if (isset($namespaces[''])) {
$defaultNamespaceUrl = $namespaces[''];
$xml->registerXPathNamespace('default', $defaultNamespaceUrl);
$nsprefix = 'default:';
} else {
$nsprefix = '';
}
$somethings = $xml->xpath('//'.$nsprefix.'Something');
echo count($somethings).' times found';
When you just want your xml, parsed to be used, and you don't care for any namespaces,
you just remove them. Regular expressions are good, and way faster than my method below.
But for a safer approach when removing namespaces, one could parse the xml with SimpleXML and ask for the namespaces it has, like below:
$xml = '...';
$namespaces = simplexml_load_string($xml)->getDocNamespaces(true);
//The line bellow fetches default namespace with empty key, like this: '' => 'url'
//So we remove any default namespace from the array
$namespaces = array_filter(array_keys($namespaces), function($k){return !empty($k);});
$namespaces = array_map(function($ns){return "$ns:";}, $namespaces);
$ns_clean_xml = str_replace("xmlns=", "ns=", $xml);
$ns_clean_xml = str_replace($namespaces, array_fill(0, count($namespaces), ''), $ns_clean_xml);
$xml_obj = simplexml_load_string($ns_clean_xml);
Thus you hit replace only for the namespaces avoiding to remove anything else the xml could have.
Actually I am using it as a method:
function refined_simplexml_load_string($xml_string) {
if(false === ($x1 = simplexml_load_string($xml_string)) ) return false;
$namespaces = array_keys($x1->getDocNamespaces(true));
$namespaces = array_filter($namespaces, function($k){return !empty($k);});
$namespaces = array_map(function($ns){return "$ns:";}, $namespaces);
return simplexml_load_string($ns_clean_xml = str_replace(
array_merge(["xmlns="], $namespaces),
array_merge(["ns="], array_fill(0, count($namespaces), '')),
$xml_string
));
}
To remove the namespace completely, you'll need to use Regular Expressions (RegEx). For example:
$feed = file_get_contents("http://www.sitepoint.com/recent.rdf");
$feed = preg_replace("/<.*(xmlns *= *[\"'].[^\"']*[\"']).[^>]*>/i", "", $feed); // This removes ALL default namespaces.
$xml_feed = simplexml_load_string($feed);
Then you've stripped any xml namespaces before you load the XML (be careful with the regex through, because if you have any fields with something like:
<![CDATA[ <Transfer xmlns="http://redeux.example.com">cool.</Transfer> ]]>
Then it will strip the xmlns from inside the CDATA which may lead to unexpected results.