I have currently an xml file that is formatted as follows:
<SKUQuestions>
<SKUQuestion>
<Tag>FrontCamaraWorks</Tag>
<Question>Does the front Camera Work?</Question>
<Answer>Yes</Answer>
</SKUQuestion>
<SKUQuestion>
<Tag>BackCamaraWorks</Tag>
<Question>Does the Rear Camera Work?</Question>
<Answer>No</Answer>
</SKUQuestion>
</SKUQuestions>
I am currently getting the info from the "answer" nodes by calling where it occurs in the xml for example:
$qFrontCameraWorks = $data['skuQuestions']->SKUQuestion[4];
$data['frontCameraWorks'] = str_replace("'", "\'", $qFrontCameraWorks->Answer);
my problem is the xml files don't always come to me with the questions in the same order.
So, I was wanting to figure a way to query within a "SKUquestion" node if the "tag" is one thing then it would return the value of the "answer" node within that same SKUquestion node.
for example if the tag is "FrontCamaraWorks" then $data['frontCameraWorks'] is yes.
Simply run an XPath query expression. As information, XPath is a declarative language used to reference parts of XML documents. PHP's SimpleXMLElement class can run XPath queries returned in arrays:
$xmlstr = "<SKUQuestions>
<SKUQuestion>
<Tag>FrontCamaraWorks</Tag>
<Question>Does the front Camera Work?</Question>
<Answer>Yes</Answer>
</SKUQuestion>
<SKUQuestion>
<Tag>BackCamaraWorks</Tag>
<Question>Does the Rear Camera Work?</Question>
<Answer>No</Answer>
</SKUQuestion>
</SKUQuestions>";
# LOAD STRING XML
$xml = new SimpleXMLElement($xmlstr);
# RUN XPATH QUERY
$queryResult = $xml->xpath('//SKUQuestion[Tag="FrontCamaraWorks"]/Answer');
# OUTPUT VALUE
echo $queryResult[0];
# Yes
Alternatively, you can use PHP's DOMXPath using its query() method which returns a DOMNodelist (not array):
# LOAD STRING XML
$xmldoc = new DOMDocument();
$xmldoc->loadXML($xmlstr);
# INITIALIZE AND RUN QUERY
$xpathvar = new DOMXPath($xmldoc);
$queryResult = $xpathvar->query('//SKUQuestion[Tag="FrontCamaraWorks"]/Answer');
# ITERATE DOM NODE LIST
foreach($queryResult as $result){
echo $result->nodeValue;
}
# Yes
You can refer this link for the details: http://php.net/manual/en/function.simplexml-load-file.php.
I am giving you an example also for your understanding.
Convert the XML string ($buffer) into a simplified array ignore attributes and grouping child-elements with the same names:
function XML2Array(SimpleXMLElement $parent)
{
$array = array();
foreach ($parent as $name => $element) {
($node = & $array[$name])
&& (1 === count($node) ? $node = array($node) : 1)
&& $node = & $node[];
$node = $element->count() ? XML2Array($element) : trim($element);
}
return $array;
}
$xml = simplexml_load_string($buffer);
$array = XML2Array($xml);
$array = array($xml->getName() => $array);
Result:
Array
(
[SKUQuestions] => Array
(
[SKUQuestion] => Array
(
[Tag] =>
[Question] =>
[Answer] =>
)
)
)
Then, you can refer the answer tag irrespective of your skuquestions ordering. I hope it will help.
Related
I need to save some values from XML.
First step - I get the structure:
$xml = $dom_xml->saveXML();
$xml_ = new \SimpleXMLElement($xml);
dd($xml_);
Here TextFrame has 8 arrays. Each of them has PathPointType, which has
4 more arrays with 3 attributes each. And these attributes I need from each TextFrame.
I can get, for instance, Anchor value doing this:
$res = $xml_
->Spread
->TextFrame
->Properties
->PathGeometry
->GeometryPathType
->PathPointArray
->PathPointType
->attributes();
dd($res['Anchor']);
(BTW: is there more prettier way to get it?)
But the question is - how is it possible to loop through all arrays and save values separately for each array?
I assume here has to be a multidimensional foreach loop in conjunction with for loop?
Or is better to achieve it using DOMDocument?
As it looks as though you are starting off with DOMDocument (as you are using $dom_xml->saveXML() to generate the XML), it may be easier to continue using it and it also has some easy features for getting the details your after.
Using getElementsByTagName() allows you to get a list of the elements with a specific tag name from a start point, so starting with $dom_xml, get all of the <TextFrame> elements. Then foreach() over this list and using this element as a start point, use getElementsByTagName("PathPointType") to get the nested <PathPointType> elements. At this point you can then use getAttribute("Anchor") for each of the attributes you need from the <PathPointType> elements...
$textFrames = $dom_xml->getElementsByTagName("TextFrame");
foreach ( $textFrames as $frame ) {
$pathPointTypes = $frame->getElementsByTagName("PathPointType");
foreach ( $pathPointTypes as $type ) {
echo $type->getAttribute("Anchor").PHP_EOL;
}
}
Edit
You can extend the code to build an array of frames and then the anchors within that. This code also stores the anchor in an associative array so that if you add the other attributes, you can add them here (or remove it if you don't need another layer of detail)...
$frames =[];
foreach ( $textFrames as $frame ) {
$anchors = [];
$pathPointTypes = $frame->getElementsByTagName("PathPointType");
foreach ( $pathPointTypes as $type ) {
$anchors[] = ['Anchor' => $type->getAttribute("Anchor")];
}
$frames[] = $anchors;
}
Also if you have some way of identifying the frames, you could create an associative array at that level as well...
$frames[$frameID] = $anchors;
As a complement to the existing answer from Nigel Ren, I thought I'd show how the same loops look with SimpleXML.
Firstly, note that you don't need to convert the XML to string and back if you want to switch between DOM and SimpleXML for any reason, you can use simplexml_import_dom which just swaps out the interface:
$sxml = simplexml_import_dom($dom_xml);
Next we need our TextFrame elements; we could either step through the structure explicitly, as you had before:
$textFrames = $sxml->Spread->TextFrame;
Or we could use XPath to search for matching tag names within our current node (. is the current element, and // means "any descendant":
$textFrames = $sxml->xpath('.//TextFrame');
The first will give you a SimpleXMLElement object, and the second an array, but either way, you can use foreach to go through the matches.
This time we definitely want an XPath expression to get the PathPointType nodes, to avoid all the nested loops through levels we're not that interested in:
foreach ( $textFrames as $frame ) {
$pathPointTypes = $frame->xpath('.//PathPointType');
foreach ( $pathPointTypes as $type ) {
echo $type['Anchor'] . PHP_EOL;
}
}
Note that you don't need to call $type->attributes(); unless you're dealing with namespaces, all you need to get an attribute is $node['AttributeName']. Beware that attributes in SimpleXML are objects though, so you'll often want to force them to be strings with (string)$node['AttributeName'].
To take the final example, you might then have something like this:
$frames = [];
foreach ( $sxml->Spread->TextFrame as $frame ) {
$anchors = [];
$pathPointTypes = $frame->xpath('.//PathPointType');
foreach ( $pathPointTypes as $type ) {
$anchors[] = ['Anchor' => (string)$type['Anchor']];
}
$frames[] = $anchors;
}
I'm parsing some XML with PHP DOM extension in order to store the data in some other form. Quite unsurprisingly, when I parse an element I pretty often need to obtain all children elements of some name. There is the method DOMElement::getElementsByTagName($name), but it returns all descendants with that name, not just immediate children. There is also the property DOMNode::$childNodes but (1) it contains node list, not element list, and even if I managed to turn the list items into elements (2) I'd still need to check all of them for the name. Is there really no elegant solution to get only the children of some specific name or am I missing something in the documentation?
Some illustration:
<?php
DOMDocument();
$document->loadXML(<<<EndOfXML
<a>
<b>1</b>
<b>2</b>
<c>
<b>3</b>
<b>4</b>
</c>
</a>
EndOfXML
);
$bs = $document
->getElementsByTagName('a')
->item(0)
->getElementsByTagName('b');
foreach($bs as $b){
echo $b->nodeValue . "\n";
}
// Returns:
// 1
// 2
// 3
// 4
// I'd like to obtain only:
// 1
// 2
?>
simple iteration process
$parent = $p->parentNode;
foreach ( $parent->childNodes as $pp ) {
if ( $pp->nodeName == 'p' ) {
if ( strlen( $pp->nodeValue ) ) {
echo "{$pp->nodeValue}\n";
}
}
}
An elegant manner I can imagine would be using a FilterIterator that is suitable for the job. Exemplary one that is able to work on such a said DOMNodeList and (optionally) accepting a tagname to filter for as an exemplary DOMElementFilter from the Iterator Garden does:
$a = $doc->getElementsByTagName('a')->item(0);
$bs = new DOMElementFilter($a->childNodes, 'b');
foreach($bs as $b){
echo $b->nodeValue . "\n";
}
This will give the results you're looking for:
1
2
You can find DOMElementFilter in the Development branch now. It's perhaps worth to allow * for any tagname as it's possible with getElementsByTagName("*") as well. But that's just some commentary.
Hier is a working usage example online: https://eval.in/57170
My solution used in a production:
Finds a needle (node) in a haystack (DOM)
function getAttachableNodeByAttributeName(\DOMElement $parent = null, string $elementTagName = null, string $attributeName = null, string $attributeValue = null)
{
$returnNode = null;
$needleDOMNode = $parent->getElementsByTagName($elementTagName);
$length = $needleDOMNode->length;
//traverse through each existing given node object
for ($i = $length; --$i >= 0;) {
$needle = $needleDOMNode->item($i);
//only one DOM node and no attributes specified?
if (!$attributeName && !$attributeValue && 1 === $length) return $needle;
//multiple nodes and attributes are specified
elseif ($attributeName && $attributeValue && $needle->getAttribute($attributeName) === $attributeValue) return $needle;
}
return $returnNode;
}
Usage:
$countryNode = getAttachableNodeByAttributeName($countriesNode, 'country', 'iso', 'NL');
Returns DOM element from parent countries node by specified attribute iso using country ISO code 'NL', basically like a real search would do. Find a certain country by it's name in an array / object.
Another usage example:
$productNode = getAttachableNodeByAttributeName($products, 'partner-products');
Returns DOM node element containing only single (root) node, without searching by any attribute.
Note: for this you must make sure that root nodes are unique by elements' tag name, e.g. countries->country[ISO] - countries node here is unique and parent to all child nodes.
I'm parsing some XML with PHP DOM extension in order to store the data in some other form. Quite unsurprisingly, when I parse an element I pretty often need to obtain all children elements of some name. There is the method DOMElement::getElementsByTagName($name), but it returns all descendants with that name, not just immediate children. There is also the property DOMNode::$childNodes but (1) it contains node list, not element list, and even if I managed to turn the list items into elements (2) I'd still need to check all of them for the name. Is there really no elegant solution to get only the children of some specific name or am I missing something in the documentation?
Some illustration:
<?php
DOMDocument();
$document->loadXML(<<<EndOfXML
<a>
<b>1</b>
<b>2</b>
<c>
<b>3</b>
<b>4</b>
</c>
</a>
EndOfXML
);
$bs = $document
->getElementsByTagName('a')
->item(0)
->getElementsByTagName('b');
foreach($bs as $b){
echo $b->nodeValue . "\n";
}
// Returns:
// 1
// 2
// 3
// 4
// I'd like to obtain only:
// 1
// 2
?>
simple iteration process
$parent = $p->parentNode;
foreach ( $parent->childNodes as $pp ) {
if ( $pp->nodeName == 'p' ) {
if ( strlen( $pp->nodeValue ) ) {
echo "{$pp->nodeValue}\n";
}
}
}
An elegant manner I can imagine would be using a FilterIterator that is suitable for the job. Exemplary one that is able to work on such a said DOMNodeList and (optionally) accepting a tagname to filter for as an exemplary DOMElementFilter from the Iterator Garden does:
$a = $doc->getElementsByTagName('a')->item(0);
$bs = new DOMElementFilter($a->childNodes, 'b');
foreach($bs as $b){
echo $b->nodeValue . "\n";
}
This will give the results you're looking for:
1
2
You can find DOMElementFilter in the Development branch now. It's perhaps worth to allow * for any tagname as it's possible with getElementsByTagName("*") as well. But that's just some commentary.
Hier is a working usage example online: https://eval.in/57170
My solution used in a production:
Finds a needle (node) in a haystack (DOM)
function getAttachableNodeByAttributeName(\DOMElement $parent = null, string $elementTagName = null, string $attributeName = null, string $attributeValue = null)
{
$returnNode = null;
$needleDOMNode = $parent->getElementsByTagName($elementTagName);
$length = $needleDOMNode->length;
//traverse through each existing given node object
for ($i = $length; --$i >= 0;) {
$needle = $needleDOMNode->item($i);
//only one DOM node and no attributes specified?
if (!$attributeName && !$attributeValue && 1 === $length) return $needle;
//multiple nodes and attributes are specified
elseif ($attributeName && $attributeValue && $needle->getAttribute($attributeName) === $attributeValue) return $needle;
}
return $returnNode;
}
Usage:
$countryNode = getAttachableNodeByAttributeName($countriesNode, 'country', 'iso', 'NL');
Returns DOM element from parent countries node by specified attribute iso using country ISO code 'NL', basically like a real search would do. Find a certain country by it's name in an array / object.
Another usage example:
$productNode = getAttachableNodeByAttributeName($products, 'partner-products');
Returns DOM node element containing only single (root) node, without searching by any attribute.
Note: for this you must make sure that root nodes are unique by elements' tag name, e.g. countries->country[ISO] - countries node here is unique and parent to all child nodes.
I am very sorry if my question doesn't make sense as this is my first question, however i've been going through the questions posted for a while now.
right i have a XML structure of
<category>
<id>123456</id>
<name>Sales</name>
<categories>
<category>
<id>2345</id>
<name>UK House prices</name>
</category>
<category>
<id>3456</id>
<name>Property Market Surveys</name>
</category>
</categories>
I'd want a function to parse through the above xml and generate an array idealy to look something like this
array(
"123456" =>
array(
"id" => "2345",
"name" => "UK House prces",
),
array(
"id" => "3456",
"name" => "Property Market Surveys",
)
);
i must then be able to extract data of the array and insert it into a table with parent child relation.
Please let me know if you need more info to make this question complete.
Thanks a lot for your time.
Regards.
Why do you need it? If you want to access the XML tree in a simple fashion (similar to arrays, except array notation is only used when there are siblings with the same element name, otherwise the property notation is used), you can use SimpleXML.
This function seems to work, if you google php xml to array you get loads of good relevant results back.
Here is a good starting point: (sourced from http://snipplr.com/view/17886/xml-to-array/)
Just specify the XML as the parameter and it returns an array:
function xml2array($xml,$recursive = false) {
if (!$recursive ) { $array = simplexml_load_string ($xml); }
else { $array = $xml ; }
$newArray = array();
$array = $array ;
foreach ($array as $key => $value) {
$value = (array) $value;
if (isset($value[0])) { $newArray[$key] = trim($value[0]); }
else { $newArray[$key][] = XML2Array($value,true) ; }
}
return $newArray;
}
Rewriting the last solution with a better indexing of nodes ( by name ) and reducing the number of parameters :
function xml2Array( $xml )
{
$xml = ( array )( is_string( $xml ) ? simplexml_load_string( $xml ) : $xml ) ;
$parse = array() ;
foreach( $xml as $key => $value )
{
$parse[$key] = is_string( $value )
? trim( $value )
: $this->xml2Array( $value )
;
}
return $parse ;
}
The code below helps be to get the WHOLE XML and put it into an array. What I'm wondering is, what would be a good way to get the XML only from item 3 - 6 or any arbitrary range instead of the whole document.
$d = new DOMDocument();
$d->load('http://news.google.com/?output=rss');
foreach ($d->getElementsByTagName('item') as $t) {
$list = array ( 'title' => $t->getElementsByTagName('title')->item(0)->nodeValue);
array_push($mt_arr, $list);
}
Thanks
You can use Xpath.
You can either use DOMXpath or use the xpath method to create an Xpath query that will return the subset of nodes.
$d->xpath('/SOME/XPATH/STATEMENT');