What to use for XML parsing / reading in PHP4 - php

Unfortunatly I have to work in a older web application on a PHP4 server;
It now needs to parse a lot of XML for calling webservices (custom protocol, no SOAP/REST);
Under PHP5 I would use SimpleXML but that isn't available;
There is Dom XML in PHP4, but it isn't default any more in PHP5.
What are the other options?
I'm looking for a solution that still works on PHP5 once they migrate.
A nice extra would be if the XML can be validated with a schema.

There is a simpleXML backport avaliable: http://www.ister.org/code/simplexml44/index.html
If you can install that, then that will be the best solution.

I would second Rich Bradshaw's suggestion about the simpleXML backport, but if that's not an option, then xml_parse will do the job in PHP4, and still works after migration to 5.
$xml = ...; // Get your XML data
$xml_parser = xml_parser_create();
// _start_element and _end_element are two functions that determine what
// to do when opening and closing tags are found
xml_set_element_handler($xml_parser, "_start_element", "_end_element");
// How to handle each char (stripping whitespace if needs be, etc
xml_set_character_data_handler($xml_parser, "_character_data");
xml_parse($xml_parser, $xml);
There's a good tutorial here about parsing XML in PHP4 that may be of some use to you.

It might be a bit grass roots, but if it's applicable for the data you're working with, you could use XSLT to transform your XML in to something usable. Obviously once you upgrade to PHP5 the XSLT will still work and you can migrate as and when to DOM parsing.
Andrew

If you can use xml_parse, then go for that. It's robust, fast and compatible with PHP5. It is however not a DOM parser, but a simpler event-based one (Also called a SAX parser), so if you need to access a tree, you will have to marshal the stream into a tree your self. This is fairly simple to do; Use s stack, and push items to it on start-element and pop on end-element.

I would definitely recommend the SimpleXML backport, as long as its performance is good enough for your needs. The demonstrations of xml_parse look simple enough, but it can get very hairy very quickly in my experience. The content handler functions don't get any contextual information about where the parser is in the tree, unless you track it and provide it in the start and end tag handlers. So you're either calling functions for every start/end tag, or throwing around global variables to track where you are in the tree.
Obviously the SimpleXML backport will be a bit slower, as it's written in PHP and has to parse the whole document before it's available, but the ease of coding more than makes up for it.

Maybe also consider looking at the XML packages available in PEAR, particularly XML_Util, XML_Parser, and XML_Serializer...

XML Parser with parse_into_struct turned into a tree-array structure:
<?php
/**
* What to use for XML parsing / reading in PHP4
* #link http://stackoverflow.com/q/132233/367456
*/
$encoding = 'US-ASCII';
// https://gist.github.com/hakre/46386de578619fbd898c
$path = dirname(__FILE__) . '/time-series-example.xml';
$parser_creator = 'xml_parser_create'; // alternative creator is 'xml_parser_create_ns'
if (!function_exists($parser_creator)) {
trigger_error(
"XML Parsers' $parser_creator() not found. XML Parser "
. '<http://php.net/xml> is required, activate it in your PHP configuration.'
, E_USER_ERROR
);
return;
}
$parser = $parser_creator($encoding);
if (!$parser) {
trigger_error(sprintf('Unable to create a parser (Encoding: "%s")', $encoding), E_USER_ERROR);
return;
}
xml_parser_set_option($parser, XML_OPTION_CASE_FOLDING, 0);
xml_parser_set_option($parser, XML_OPTION_SKIP_WHITE, 1);
$data = file_get_contents($path);
if ($data === FALSE) {
trigger_error(sprintf('Unable to open file "%s" for reading', $path));
return;
}
$result = xml_parse_into_struct($parser, $data, $xml_struct_values);
unset($data);
xml_parser_free($parser);
unset($parser);
if ($result === 0) {
trigger_error(sprintf('Unable to parse data of file "%s" as XML', $path));
return;
}
define('TREE_NODE_TAG', 'tagName');
define('TREE_NODE_ATTRIBUTES', 'attributes');
define('TREE_NODE_CHILDREN', 'children');
define('TREE_NODE_TYPE_TAG', 'array');
define('TREE_NODE_TYPE_TEXT', 'string');
define('TREE_NODE_TYPE_NONE', 'NULL');
/**
* XML Parser indezies for parse into struct values
*/
define('XML_STRUCT_VALUE_TYPE', 'type');
define('XML_STRUCT_VALUE_LEVEL', 'level');
define('XML_STRUCT_VALUE_TAG', 'tag');
define('XML_STRUCT_VALUE_ATTRIBUTES', 'attributes');
define('XML_STRUCT_VALUE_VALUE', 'value');
/**
* XML Parser supported node types
*/
define('XML_STRUCT_TYPE_OPEN', 'open');
define('XML_STRUCT_TYPE_COMPLETE', 'complete');
define('XML_STRUCT_TYPE_CDATA', 'cdata');
define('XML_STRUCT_TYPE_CLOSE', 'close');
/**
* Tree Creator
* #return array
*/
function tree_create()
{
return array(
array(
TREE_NODE_TAG => NULL,
TREE_NODE_ATTRIBUTES => NULL,
TREE_NODE_CHILDREN => array(),
)
);
}
/**
* Add Tree Node into Tree a Level
*
* #param $tree
* #param $level
* #param $node
* #return array|bool Tree with the Node added or FALSE on error
*/
function tree_add_node($tree, $level, $node)
{
$type = gettype($node);
switch ($type) {
case TREE_NODE_TYPE_TEXT:
$level++;
break;
case TREE_NODE_TYPE_TAG:
break;
case TREE_NODE_TYPE_NONE:
trigger_error(sprintf('Can not add Tree Node of type None, keeping tree unchanged', $type, E_USER_NOTICE));
return $tree;
default:
trigger_error(sprintf('Can not add Tree Node of type "%s"', $type), E_USER_ERROR);
return FALSE;
}
if (!isset($tree[$level - 1])) {
trigger_error("There is no parent for level $level");
return FALSE;
}
$parent = & $tree[$level - 1];
if (isset($parent[TREE_NODE_CHILDREN]) && !is_array($parent[TREE_NODE_CHILDREN])) {
trigger_error("There are no children in parent for level $level");
return FALSE;
}
$parent[TREE_NODE_CHILDREN][] = & $node;
$tree[$level] = & $node;
return $tree;
}
/**
* Creator of a Tree Node
*
* #param $value XML Node
* #return array Tree Node
*/
function tree_node_create_from_xml_struct_value($value)
{
static $xml_node_default_types = array(
XML_STRUCT_VALUE_ATTRIBUTES => NULL,
XML_STRUCT_VALUE_VALUE => NULL,
);
$orig = $value;
$value += $xml_node_default_types;
switch ($value[XML_STRUCT_VALUE_TYPE]) {
case XML_STRUCT_TYPE_OPEN:
case XML_STRUCT_TYPE_COMPLETE:
$node = array(
TREE_NODE_TAG => $value[XML_STRUCT_VALUE_TAG],
// '__debug1' => $orig,
);
if (isset($value[XML_STRUCT_VALUE_ATTRIBUTES])) {
$node[TREE_NODE_ATTRIBUTES] = $value[XML_STRUCT_VALUE_ATTRIBUTES];
}
if (isset($value[XML_STRUCT_VALUE_VALUE])) {
$node[TREE_NODE_CHILDREN] = (array)$value[XML_STRUCT_VALUE_VALUE];
}
return $node;
case XML_STRUCT_TYPE_CDATA:
// TREE_NODE_TYPE_TEXT
return $value[XML_STRUCT_VALUE_VALUE];
case XML_STRUCT_TYPE_CLOSE:
return NULL;
default:
trigger_error(
sprintf(
'Unkonwn Xml Node Type "%s": %s', $value[XML_STRUCT_VALUE_TYPE], var_export($value, TRUE)
)
);
return FALSE;
}
}
$tree = tree_create();
while ($tree && $value = array_shift($xml_struct_values)) {
$node = tree_node_create_from_xml_struct_value($value);
if (NULL === $node) {
continue;
}
$tree = tree_add_node($tree, $value[XML_STRUCT_VALUE_LEVEL], $node);
unset($node);
}
if (!$tree) {
trigger_error('Parse error');
return;
}
if ($xml_struct_values) {
trigger_error(sprintf('Unable to process whole parsed XML array (%d elements left)', count($xml_struct_values)));
return;
}
// tree root is the first child of level 0
print_r($tree[0][TREE_NODE_CHILDREN][0]);
Output:
Array
(
[tagName] => dwml
[attributes] => Array
(
[version] => 1.0
[xmlns:xsd] => http://www.w3.org/2001/XMLSchema
[xmlns:xsi] => http://www.w3.org/2001/XMLSchema-instance
[xsi:noNamespaceSchemaLocation] => http://www.nws.noaa.gov/forecasts/xml/DWMLgen/schema/DWML.xsd
)
[children] => Array
(
[0] => Array
(
[tagName] => head
[children] => Array
(
[0] => Array
(
[tagName] => product
[attributes] => Array
(
[srsName] => WGS 1984
[concise-name] => time-series
[operational-mode] => official
)
[children] => Array
(
[0] => Array
(
[tagName] => title
[children] => Array
(
[0] => NOAA's National Weather Service Forecast Data
)
)
[1] => Array
(
[tagName] => field
[children] => Array
(
[0] => meteorological
)
)
[2] => Array
(
[tagName] => category
[children] => Array
(
[0] => forecast
)
)
[3] => Array
(
[tagName] => creation-date
[attributes] => Array
(
[refresh-frequency] => PT1H
)
[children] => Array
(
[0] => 2013-11-02T06:51:17Z
)
)
)
)
[1] => Array
(
[tagName] => source
[children] => Array
(
[0] => Array
(
[tagName] => more-information
[children] => Array
(
[0] => http://www.nws.noaa.gov/forecasts/xml/
)
)
[1] => Array
(
[tagName] => production-center
[children] => Array
(
[0] => Meteorological Development Laboratory
[1] => Array
(
[tagName] => sub-center
[children] => Array
(
[0] => Product Generation Branch
)
)
)
)
[2] => Array
(
[tagName] => disclaimer
[children] => Array
(
[0] => http://www.nws.noaa.gov/disclaimer.html
)
)
[3] => Array
(
[tagName] => credit
[children] => Array
(
[0] => http://www.weather.gov/
)
)
[4] => Array
(
[tagName] => credit-logo
[children] => Array
(
[0] => http://www.weather.gov/images/xml_logo.gif
)
)
[5] => Array
(
[tagName] => feedback
[children] => Array
(
[0] => http://www.weather.gov/feedback.php
)
)
)
)
)
)
[1] => Array
(
[tagName] => data
[children] => Array
(
[0] => Array
(
[tagName] => location
[children] => Array
(
[0] => Array
(
[tagName] => location-key
[children] => Array
(
[0] => point1
)
)
[1] => Array
(
[tagName] => point
[attributes] => Array
(
[latitude] => 40.00
[longitude] => -120.00
)
)
)
)
[1] => Array
(
[tagName] => moreWeatherInformation
[attributes] => Array
(
[applicable-location] => point1
)
[children] => Array
(
[0] => http://forecast.weather.gov/MapClick.php?textField1=40.00&textField2=-120.00
)
)
[2] => Array
(
[tagName] => time-layout
[attributes] => Array
(
[time-coordinate] => local
[summarization] => none
)
[children] => Array
(
[0] => Array
(
[tagName] => layout-key
[children] => Array
(
[0] => k-p24h-n4-1
)
)
[1] => Array
(
[tagName] => start-valid-time
[children] => Array
(
[0] => 2013-11-02T08:00:00-07:00
)
)
[2] => Array
(
[tagName] => end-valid-time
[children] => Array
(
[0] => 2013-11-02T20:00:00-07:00
)
)
[3] => Array
(
[tagName] => start-valid-time
[children] => Array
(
[0] => 2013-11-03T07:00:00-08:00
)
)
[4] => Array
(
[tagName] => end-valid-time
[children] => Array
(
[0] => 2013-11-03T19:00:00-08:00
)
)
[5] => Array
(
[tagName] => start-valid-time
[children] => Array
(
[0] => 2013-11-04T07:00:00-08:00
)
)
[6] => Array
(
[tagName] => end-valid-time
[children] => Array
(
[0] => 2013-11-04T19:00:00-08:00
)
)
[7] => Array
(
[tagName] => start-valid-time
[children] => Array
(
[0] => 2013-11-05T07:00:00-08:00
)
)
[8] => Array
(
[tagName] => end-valid-time
[children] => Array
(
[0] => 2013-11-05T19:00:00-08:00
)
)
)
)
[3] => Array
(
[tagName] => time-layout
[attributes] => Array
(
[time-coordinate] => local
[summarization] => none
)
[children] => Array
(
[0] => Array
(
[tagName] => layout-key
[children] => Array
(
[0] => k-p24h-n5-2
)
)
[1] => Array
(
[tagName] => start-valid-time
[children] => Array
(
[0] => 2013-11-01T20:00:00-07:00
)
)
[2] => Array
(
[tagName] => end-valid-time
[children] => Array
(
[0] => 2013-11-02T09:00:00-07:00
)
)
[3] => Array
(
[tagName] => start-valid-time
[children] => Array
(
[0] => 2013-11-02T19:00:00-07:00
)
)
...
[10] => Array
(
[tagName] => end-valid-time
[children] => Array
(
[0] => 2013-11-06T08:00:00-08:00
)
)
)
)
[4] => Array
(
[tagName] => time-layout
[attributes] => Array
(
[time-coordinate] => local
[summarization] => none
)
[children] => Array
(
[0] => Array
(
[tagName] => layout-key
[children] => Array
(
[0] => k-p12h-n9-3
)
)
[1] => Array
(
[tagName] => start-valid-time
[children] => Array
(
[0] => 2013-11-01T17:00:00-07:00
)
)
...

Related

json array sort by value in php

I have JSON arrays of objects. I am trying to sort an array using usort. I want to use value field from field_listing_order. It sorted using value. I am missing something but not able to figure it out. please review the code. Thanks!
stdClass Object
(
[node_title] => abc
[nid] => 2281
[field_api_order_value] => 201
[field_node_entity_type] => node
[_data] => Array
(
[nid] => Array
(
[entity_type] => node
[entity] => stdClass Object
(
[title] => abc
[field_listing_order] => Array
(
[und] => Array
(
[0] => Array
(
[value] => 8
[format] =>
[safe_value] => 8
)
)
)
)
)
)
)
stdClass Object
(
[node_title] => abc
[nid] => 2243
[field_api_order_value] => 204
[field_node_entity_type] => node
[_data] => Array
(
[nid] => Array
(
[entity_type] => node
[entity] => stdClass Object
(
[title] => abc
[field_listing_order] => Array
(
[und] => Array
(
[0] => Array
(
[value] => 3
[format] =>
[safe_value] => 3
)
)
)
)
)
)
) stdClass Object
(
[node_title] => abc
[nid] => 2431
[field_api_order_value] => 242
[field_node_entity_type] => node
[_data] => Array
(
[nid] => Array
(
[entity_type] => node
[entity] => stdClass Object
(
[title] => abc
[field_listing_order] => Array
(
[und] => Array
(
[0] => Array
(
[value] => 1
[format] =>
[safe_value] => 1
)
)
)
)
)
)
)
and So on ...
foreach ($view->result as $result) {
$node = $result->_data['nid']['entity'];
$listing_order = $node->field_listing_order[LANGUAGE_NONE][0];
.....
// code goes here and it works well. sorting issue
}
usort ($node->field_listing_order[LANGUAGE_NONE][0], function($a, $b){
return strcmp($a->value, $b->value);
}); ?>
If you need to sort all the nodes using the field_listing_order value, you need to compare the values along the full path, from the first object to the value:
usort($view->result, function($a, $b) {
$la = $a->_data['nid']['entity']->field_listing_order[LANGUAGE_NONE][0]['value'];
$lb = $b->_data['nid']['entity']->field_listing_order[LANGUAGE_NONE][0]['value'];
return $la - $lb ;
});
In this case $a and $b are two different nodes which could be compared. That why you should compare the field_listing_order's value of these nodes. The answer is working with $la- $lb

Arrange an array recursively

I'm trying to arrange an array in levels. This is my array:
Array
(
[0] => Array(
[0] => Array(
[id] => 971249312[name] => Wolverine
)
[children] => Array(
[0] => Array(
[0] => Array(
[id] => 735327624[name] => Ciclop
)
[children] => Array()
)
)
)
[1] => Array(
[0] => Array(
[id] => 1926833684[name] => Gambit
)
[children] => Array()
)
[2] => Array(
[0] => Array(
[id] => 51194629[name] => Quicksilver
)
[children] => Array()
)
)
See that the first position of array has 3 elements - this must be the level 0. The first position of these elements must be the level 1. The children of these elements are the next level and so on.
I canĀ“t figure out how to arrange it.
the expected output:
Array
(
["level_1"] => Array
(
[0] => Array
(
[id] => 971249312
[name] => Wolverine
)
[1] => Array
(
[id] => 1926833684
[name] => Gambit
)
[2] => Array
(
[id] => 51194629
[name] => Quicksilver
)
)
["level_2"] => Array
(
[0] => Array
(
[id] => 735327624
[name] => Ciclop
)
)
)
Another recursive tree walk.
I scan the tree 'depth first' so I need to keep track of the current 'level'.
Demonstration at eval.in
Tree scan routine:
/**
* Recursive scan of the tree
*
* #node array Current Node to be processed
* #level integer Current depth of the tree
* output array reference to where to store the details
*
* #return void
*/
function scanNode($node, $level, &$output)
{
$outLevelIdx = 'level_'. $level;
foreach ($node as $idx => $info) {
$parent = current($info);
$output[$outLevelIdx][] = array('id' => $parent['id'], 'name' => $parent['name']);
if (!empty($info['children'])) { // go scan the children
scanNode($info['children'], $level + 1, $output);
}
}
}
Run the scan:
/*
* Output array in here - pass as a reference
*/
$output = array();
// scan the full tree
scanNode($source, 0, $output);
Sample output:
output
Array
(
[level_0] => Array
(
[0] => Array
(
[id] => 971249312
[name] => Wolverine
)
[1] => Array
(
[id] => 1926833684
[name] => Gambit
)
[2] => Array
(
[id] => 51194629
[name] => Quicksilver
)
)
[level_1] => Array
(
[0] => Array
(
[id] => 735327624
[name] => Ciclop
)
)
)
If your desired output is
Array
(
[0] => Array
(
[id] => 971249312
[name] => Wolverine
[children] => Array
(
)
)
[1] => Array
(
[id] => 971249312
[name] => Wolverine
[children] => Array
(
)
)
[2] => Array
(
[id] => 971249312
[name] => Wolverine
[children] => Array
(
)
)
)
Then your code should be
$newArray = [];
foreach ($givenArray as $key => $value) {
$newArray[$key]['id'] = $value[0]['id'];
$newArray[$key]['name'] = $value[0]['name'];
$newArray[$key]['children'] = $value['children'];
}
AS per your desired output
This function used to scan all the node and provide as per your requirment.
OUTPUT
$newArray = [];
myfunction($a, 0,$newArray);
function myfunction($loop, $level, &$newArray) {
$index = "level_".$level;
$i = 0;
foreach ($loop as $key => $value) {
foreach ($value as $key1 => $value1) {
if($key1 !== 'children'){
$newArray[$index][$i] = ['id' => $value1['id'], 'name' => $value1['name']];
$i++;
}
}
if (isset($value['children']) && !empty($value['children'])) {
myfunction($value['children'], $level + 1, $newArray);
}
}
}
print_r($newArray);exit;

recursively find all array values

So I have a big response from an API in a multi-dimensional array, and I need to find all the instances of a certain key->value pair, specifically ['type'] => PhotoField My task would be easy if they were all the same depth, but they vary, so I am using a recursive function to get all the key value pairs of a specific value. I have morphed a standard recursive array searching algorithm for my purposes. But I still have the problem that when it pushes the path of each instance to the $path array, it just merges to the path of the previous instance.
What I really need is for each instance's path to be a subarray within $path.
Here is my function:
function array_searchRecursive($needle, $haystack, $strict=false, $path=array() )
{
if(!array_key_exists('elements', $haystack)) {
return false;
}
foreach( $haystack['elements'] as $key => $val ) {
if( is_array($val) && $subPath = array_searchRecursive($needle, $val, $strict, $path) ) {
$path = array_merge($path, array($key), $subPath);
return $path;
} elseif( (!$strict && $val['type'] == $needle) || ($strict && $val['type'] === $needle) ) {
$path[] = $val['key'];
}
}
if (!(empty($path))){
return $path;
}
return false;
}
I call it with:
array_searchRecursive($resp['form']);
And here is some sample data:
$resp =
Array
(
[form] => Array
(
[name] => Site Inspection
[elements] => Array
(
[0] => Array
(
[type] => Section
[key] => 86d2
[elements] => Array
(
[0] => Array
(
[type] => ChoiceField
[key] => 450c
)
)
)
[1] => Array
(
[type] => Section
[key] => 6021
[elements] => Array
(
[0] => Array
(
[type] => TextField
[key] => c8e5
)
[1] => Array
(
[type] => PhotoField
[key] => 01dd
[label] => Photos of Protective Structure
)
[2] => Array
(
[type] => PhotoField
[key] => 8e1c
[label] => Photos of Degradation to Protective Structures
)
)
)
[2] => Array
(
[type] => Section
[key] => 9335
[elements] => Array
(
[0] => Array
(
[type] => TextField
[key] => b614
)
[1] => Array
(
[type] => Repeatable
[key] => 6b00
(
[0] => Array
(
[type] => TextField
[key] => b646
)
[1] => Array
(
[type] => PhotoField
[key] => 9747
)
)
)
)
)
)
)
Sincere thanks for any help. It is greatly appreciated.
This should work:
foreach($resp['form']['elements'] as $v)
{
if($v['type'] === 'PhotoField')
{
// we found it!
}
}

How to remove branches that don't contain a certain value in a php array

I've spent the day playing with deceze's answer but I'm no closer to making it work. I may have part of it, but not sure how to get recursion in array_filter.
My Array looks like this (sample):
Array
(
[name] => root
[ChildCats] => Array
(
[0] => Array
(
[name] => Air Conditioning
[ChildCats] => Array
(
[0] => Array
(
[name] => Ducted Air Conditioning
[ChildCats] => Array
(
[0] => Array
(
[name] => Supply & Install
[ChildCats] => Array
(
[0] => Array
(
[name] => Daiken
[S] => 6067
)
)
)
[1] => Array
(
[name] => Supply Only
[ChildCats] => Array
(
[0] => Array
(
[name] => Mitsubishi
[S] => 6026
)
)
)
)
)
[1] => Array
(
[name] => Split System Air Conditioning
[ChildCats] => Array
(
[0] => Array
(
[name] => Supply & Install
[ChildCats] => Array
(
[0] => Array
(
[name] => Daiken
[S] => 6067
)
[1] => Array
(
[name] => Fujitsu Split Air Conditioning Systems
[S] => 6464
)
[2] => Array
(
[name] => Mitsubishi Electric Split Air Conditioning Systems
[S] => 6464
)
)
)
)
)
)
)
[1] => Array
(
[name] => Appliance / White Goods
[ChildCats] => Array
(
[0] => Array
(
[name] => Clearance
[S] => 6239
)
[1] => Array
(
[name] => Cooktops
[ChildCats] => Array
(
[0] => Array
(
[name] => Ceramic Cooktops
[S] => 6239
)
[1] => Array
(
[name] => Element Cooktops
[S] => 6067
)
[2] => Array
(
[name] => Gas Cooktops
[S] => 6239
)
[3] => Array
(
[name] => Induction Cooktops
[S] => 6239
)
)
)
)
)
Now lets say I try to extract just the parts of the array relevent to the following keypair:
S => 6067.
I'd like the result to look like:
Array
(
[name] => root
[ChildCats] => Array
(
[0] => Array
(
[name] => Air Conditioning
[ChildCats] => Array
(
[0] => Array
(
[name] => Ducted Air Conditioning
[ChildCats] => Array
(
[0] => Array
(
[name] => Supply & Install
[ChildCats] => Array
(
[0] => Array
(
[name] => Daiken
[S] => 6067
)
)
)
)
)
[1] => Array
(
[name] => Split System Air Conditioning
[ChildCats] => Array
(
[0] => Array
(
[name] => Supply & Install
[ChildCats] => Array
(
[0] => Array
(
[name] => Daiken
[S] => 6067
)
)
)
)
)
)
)
[1] => Array
(
[name] => Appliance / White Goods
[ChildCats] => Array
(
[0] => Array
(
[name] => Cooktops
[ChildCats] => Array
(
[0] => Array
(
[name] => Element Cooktops
[S] => 6067
)
)
)
)
)
)
)
What I cannot get my head arround is should I be creating a new array or using array filter.
Playing with deceze code I've got the search working using the following:
function recursive_assoc_in_array(array $haystack, array $needle, $childKey = 'ChildCats') {
if (array_intersect_assoc($haystack, $needle)) {
echo "Found via array_intersect_assoc ".$haystack[name]."\n";
return true;
}
foreach ($haystack[$childKey] as $child) {
if (recursive_assoc_in_array($child, $needle, $childKey)) return true;
}
return false;
}
But if I try to process with,
$array = array_filter($array, function (array $values) {
return recursive_assoc_in_array($values, array('S' => '6067'));
});
I get the original array which leads me to think I have to get recursion running on the array_filter query.
At this point I just go blank.
Additionally, the array keys will need to be reindexed on the produced new array. Any ideas?
--Additional 7/7/14
How about if I try to build a new array from the old one?
I'm trying:
function exploreArrayandAdd($Array) {
if ($Array['ChildCats']) {
foreach ($Array['ChildCats'] as $key => $value) {
$NewArray['ChildCats'][] = exploreArrayandAdd($value);
}
} else {
if ($Array['S'] == 6026) {
//echo "1";
return $Array;
}
}
}
But cannot work out how to pass the new array out of the function?
Tried removing branches that don't match using:
function exploreArray(&$Array) {
if ($Array['ChildCats']) {
foreach ($Array['ChildCats'] as $key => $value) {
$result = exploreArray($Array['ChildCats'][$key]);
if ($result === false)
unset($Array['ChildCats'][$key]);
}
} else {
// print_r($Array);
if ($Array['S'] == 6026) {
return true;
} else {
unset($Array);
return false;
}
}
//if ($NoChildCat==true) print_r($Array);
}
But I believe it is the wrong way as it does work at the bottom of the array but not back up towards the top as siblings make result true.
Also this won't reindex the array keys.
function recursive_array_filter(array $array, $childKey, callable $test) {
if (isset($array[$childKey]) && is_array($array[$childKey])) {
foreach ($array[$childKey] as $key => &$child) {
if (!$child = recursive_array_filter($child, $childKey, $test)) {
unset($array[$childKey][$key]);
}
}
if (!$array[$childKey]) {
unset($array[$childKey]);
}
}
return !empty($array[$childKey]) || $test($array) ? $array : [];
}
$array = recursive_array_filter($array, 'ChildCats', function (array $array) {
return array_intersect_assoc($array, ['S' => 6026]);
});
To express the algorithm in words: you descend down into the array first, following all ChildCats branches to their end. In each level you return the values as they are back to the caller if they match your test or if they have children, or you return an emptied array (you could also return false if you prefer). If some child turns out empty, you prune it with unset.
I have implemented the test as a callback function here for best reusability of the code.

Get all parent nodes with RecursiveArrayIterator

Essentially, I want to use the
$foo = new RecursiveIteratorIterator(new RecursiveArrayIterator($haystack));
Methodology, but instead of returning a flat array for foreach()ing through, keep the structure but only return a single great-grand-child node and it's parent nodes. Is this possible in PHP?
I've been tasked with optimising some of my company's (horrific) codebase. I found a function that recurses through an array, searching for a key. I can't replace this with a simple array_search() or array_key_exists() because the custom function also returns the parent nodes of the matched (found) key, instead of just a true or false.
How can I use RecursiveArrayIterator, RecursiveIteratorIterator or failing that, other built-in functions (i.e. as little custom code as possible) to return a matching node with it's parent tree from a search array? I want to get the fastest function possible, as currently this function spends 8 seconds executing 14 thousand times, hence my requirement to use built-in functions.
My existing attempt (below) is incredibly slow.
function search_in_array($needle, $haystack) {
$path = array();
$it = new RecursiveArrayIterator($haystack);
iterator_apply($it, 'traverse', array($it, $needle, &$path));
return $path;
}
function traverse($it, $needle, &$path) {
while($it->valid()) {
$key = $it->key();
$value = $it->current();
if(strcasecmp($value['id'], $needle) === 0) {
$path[] = $key;
return;
} else if($it->hasChildren()) {
$sub = null;
traverse($it->getChildren(), $needle, &$sub);
if($sub) {
$path[$key] = $sub;
}
}
$it->next();
}
}
Example output for $needle = TVALL would look like this:
Array (
[HOMECINEMA] => Array (
[children] => Array (
[HC-VISION] => Array (
[children] => Array (
[0] => TVALL
)
)
)
)
)
The search array looks something like this (sorry for the vast-ness). There are more than two top-level nodes, but I've truncated it for brevity:
Array(2) (
[HOMECINEMA] => Array (
[id] => HOMECINEMA
[position] => 2
[title] => TV & Home Cinema
[children] => Array (
[HC-VISION] => Array (
[id] => HC-VISION
[title] => LCD & Plasma
[children] => Array (
[TVALL] => Array (
[id] => TVALL
[title] => All TVs
)
[LCD2] => Array (
[id] => LCD2
[title] => LCD/LED TVs
)
[PLASMA] => Array (
[id] => PLASMA
[title] => Plasma TVs
)
[3DTV] => Array (
[id] => 3DTV
[title] => 3D TV
)
[LED] => Array (
[id] => LED
[title] => SMART TVs
)
[PROJECTORS] => Array (
[id] => PROJECTORS
[title] => Projectors
)
[SYS-HOMECINEMATV] => Array (
[id] => SYS-HOMECINEMATV
[title] => TV Home Cinema Systems
)
)
)
[HC-SEPARATES] => Array (
[id] => HC-SEPARATES
[title] => Home Cinema Separates
[children] => Array (
[DVDRECORDERS] => Array (
[id] => DVDRECORDERS
[title] => DVD Recorders
)
[HDDVD] => Array (
[id] => HDDVD
[title] => Blu-ray
)
[AVRECEIVERS] => Array (
[id] => AVRECEIVERS
[title] => AV Receivers
)
[DVDPLAYERS] => Array (
[id] => DVDPLAYERS
[title] => DVD Players
)
[FREEVIEW] => Array (
[id] => FREEVIEW
[title] => Digital Set Top Boxes
)
[DVDPACKAGESYSTEMS-3] => Array (
[id] => DVDPACKAGESYSTEMS-3
[title] => 1 Box Home Cinema Systems
)
[HOMECINEMADEALS] => Array (
[id] => HOMECINEMADEALS
[title] => Home Cinema System Deals
)
)
)
[SPEAKER2] => Array (
[id] => SPEAKER2
[title] => Speakers
[children] => Array (
[SPEAKERPACKAGES] => Array (
[id] => SPEAKERPACKAGES
[title] => Speaker packages
)
[SOUNDBARS] => Array (
[id] => SOUNDBARS
[title] => Soundbars
)
[CENTRES] => Array (
[id] => CENTRES
[title] => Centres
)
[SUBWOOFERS] => Array (
[id] => SUBWOOFERS
[title] => Subwoofers
)
[FLOORSTANDING] => Array (
[id] => FLOORSTANDING
[title] => Floorstanders
)
[INSTALLATIONSPEAKERS] => Array (
[id] => INSTALLATIONSPEAKERS
[title] => Installation Speakers
)
[STAND-MOUNT] => Array (
[id] => STAND-MOUNT
[title] => Bookshelf Speakers
)
)
)
[HC-ACCYS] => Array (
[id] => HC-ACCYS
[title] => Accessories
[children] => Array (
[AVESSENTIALS] => Array (
[id] => AVESSENTIALS
[title] => AV Interconnects
)
[PLASMALCDSTANDSBRACKETS1] => Array (
[id] => PLASMALCDSTANDSBRACKETS1
[title] => TV Accessories
)
[RACKS] => Array (
[id] => RACKS
[title] => TV Racks
)
[HIFIRACKS] => Array (
[id] => HIFIRACKS
[title] => HiFi Racks
)
[PROJECTORACCYS] => Array (
[id] => PROJECTORACCYS
[title] => Projector Screens/Accessories
)
)
)
)
)
[SPEAKERS] => Array (
[id] => SPEAKERS
[position] => 3
[title] => Speakers
[children] => Array (
[SPK-HIFI] => Array (
[id] => SPK-HIFI
[title] => Hi-Fi
[children] => Array (
[STAND-MOUNT] => Array (
[id] => STAND-MOUNT
[title] => Bookshelf Speakers
)
[FLOORSTANDING] => Array (
[id] => FLOORSTANDING
[title] => Floorstanders
)
[INSTALLATIONSPEAKERS] => Array (
[id] => INSTALLATIONSPEAKERS
[title] => Installation Speakers
)
)
)
[SPK-HOMECINEMA] => Array (
[id] => SPK-HOMECINEMA
[title] => Home Cinema
[children] => Array (
[SPEAKERPACKAGES] => Array (
[id] => SPEAKERPACKAGES
[title] => Speaker Packages
)
[SOUNDBARS] => Array (
[id] => SOUNDBARS
[title] => Soundbars
)
[CENTRES] => Array (
[id] => CENTRES
[title] => Centres
)
[SUBWOOFERS] => Array (
[id] => SUBWOOFERS
[title] => Subwoofers
)
)
)
[SPK-ACCYS] => Array (
[id] => SPK-ACCYS
[title] => Accessories
[children] => Array (
[SPEAKERESSENTIALS1] => Array (
[id] => SPEAKERESSENTIALS
[title] => Speaker Cables
)
[SPEAKERSTANDS] => Array (
[id] => SPEAKERSTANDS
[title] => Speaker Stands
)
[SPEAKERBRACKETS] => Array (
[id] => SPEAKERBRACKETS
[title] => Speaker Wall Brackets
)
)
)
)
)
)
The example below will not necessarily be more performant (in time or memory requirements) but avoids manually recursing through the structure and shows an easier (IMHO) way to build your desired output array.
function search_in_array($needle, $haystack) {
$path = array();
$it = new RecursiveIteratorIterator(
new ParentIterator(new RecursiveArrayIterator($haystack)),
RecursiveIteratorIterator::SELF_FIRST
);
foreach ($it as $key => $value) {
if (array_key_exists('id', $value) && strcasecmp($value['id'], $needle) === 0) {
$path = array($needle);
for ($i = $it->getDepth() - 1; $i >= 0; $i--) {
$path = array($it->getSubIterator($i)->key() => $path);
}
break;
}
}
return $path;
}
Reference
RecursiveIteratorIterator::SELF_FIRST iteration mode - required to see non-"leaf" items
ParentIterator - easy way to filter out the "leaf" items
RecursiveIteratorIterator::getDepth()
RecursiveIteratorIterator::getSubIterator()
Bonus
You could also use the RecursiveIteratorIterator::setMaxDepth() method to limit the recursion to n levels deep, if your array also goes much deeper.

Categories