<?php  /*/ text_array_subkey¤5•3.php  written by and Copyright © 2009 Joe Golembieski, SoftMoon WebWare

		This program is free software: you can redistribute it and/or modify
		it under the terms of the GNU General Public License as published by
		the Free Software Foundation, either version 3 of the License, or
		(at your option) any later version.

		This program is distributed in the hope that it will be useful,
		but WITHOUT ANY WARRANTY; without even the implied warranty of
		MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
		GNU General Public License for more details.

		You should have received a copy of the GNU General Public License
		along with this program.  If not, see <http://www.gnu.org/licenses/>    /*/

// tab spacing: 2  editor width: 120  auto-word-wrap: no

Define("KEEP_TREE", 1);
Define("KEEP_KEY", 2);

Function preg_key_grep($A, $preg, $KeepKey=FALSE)  { $subA=array();
  $keys=preg_grep($preg, array_keys($A));
  if ($KeepKey)
    foreach ($keys as $k)  {$subA[$k]=$A[$k];}
  else
    foreach ($keys as $k)  {$subA[]=$A[$k];}
  return $subA;  }

Function is_iterateable($var) {return (is_array($var)  or  is_object($var) /*PHP>=5.3*/ and  !is_callable($var)/**/);}


// While these examples shows how array_subkey¤5•3 can retrieve
//  a "column" of info in a "row"-based array of arrays (of arrays of arrays....),
//  it also works with arrays of Class objects, whose members may recursively be arrays or Objects.
//  The base Object may also be a Class instead of an array, but this usually makes less sense.
// PHP 5.3 is preferred to avoid the possibility of problems noted, but most servers don't yet support 5.3.
//  With PHP 5.3, send closure functions.
//  With Object Oriented PHP, send class instances with method called 'filter'.
//  Earlier versions instead use strings declaring function names.
//  When **NOT** using PHP 5.3 or Object Oriented PHP,
//   take care that your client's array-encapsulated database will not contain indexes
//   that match function names in your code.  Perhaps prefix/postfix your function names with unusual characters,
//   or validate their database before use.

Function array_subkey¤5•3($A, $indxLevel, $IDkey, $KeepTree=FALSE)  {  $KeepKey=($KeepTree & KEEP_KEY);
  if ($indxLevel<2)  {
    if (is_array($IDkey)  and  is_array($IDkey['filter']))  $IDkey=array_shift($IDkey['filter']);
    if ($IDkey===NULL)  return $A;
//  if (is_callable($IDkey))  {  // non-object-oriented-PHP  [DANGER!  string-keyname / function-name collision]
    if ( is_object($IDkey)
    and  /* $flag=method_exists($IDkey, 'filter')   PHP < 5.3
		or */  is_callable($IDkey)     /* PHP >= 5.3   [PREFERRED] */   )  {
      $indx=$KeepTree;  $x=($flag) ? $IDkey->filter($A, $indx) : $IDkey($A, $indx);
      return ($KeepKey  and  $x!==NULL  and  $indx!==NULL) ? array($indx => $x) : $x;  }
    else
    if (is_array($IDkey))  { $subA=array();
      if (is_array($IDkey['re-index']))  {$IDkey=$IDkey['re-index'];  $reindex=TRUE;}  //while only useful with "keep key", we process this here to allow the same filter to be used with and without the "keep key" option. Move it down one line for faster performance and strickter implementation.
      if ($KeepKey)
        foreach ($IDkey as $newindex => $key)  {
//        if (is_callable($IDkey))  {  // non-object-oriented-PHP   [DANGER!  string-keyname / function-name collision]
          if (is_object($key)
          and  /* PHP < 5.3   $flag=method_exists($key, 'filter')  */
          /*or   PHP>=5.3 */ is_callable($key) )  { $indx=$KeepTree;
            if (($x=($flag) ? $key->filter($A, $indx) : $key($A, $indx))!==NULL)  $subA[$indx]=$x;  continue;  }
          if (substr($key, 0, 1)=="/")   {$subA=$subA+preg_key_grep($A, $key, KEEP_KEY);  continue;}
          if (array_key_exists($key, $A))  $subA[($reindex) ? $newindex : $key]=$A[$key];  }
      else
        foreach ($IDkey as $key)  {
//        if (is_callable($IDkey))  {  // non-object-oriented-PHP   [DANGER!  string-keyname / function-name collision]
          if (is_object($key)
          and  /* PHP < 5.3   $flag=method_exists($key, 'filter')  */
          /*or   PHP>=5.3 */ is_callable($key) )  {
            if (($x=($flag) ? $key->filter($A) : $key($A))!==NULL)  $subA[]=$x;  continue;  }
          if (substr($key, 0, 1)=="/")   {$subA=array_merge($subA, preg_key_grep($A, $key));  continue;}
          if (array_key_exists($key, $A))  $subA[]=$A[$key];  }  }
    else
    if (substr($IDkey, 0, 1)=="/")  {$subA=preg_key_grep($A, $IDkey, $KeepKey);}
    else  return ($KeepKey  and  isset($A[$IDkey])) ? array($IDkey => $A[$IDkey]) : $A[$IDkey];  }
  else  {
    if (is_array($IDkey)  and  is_array($IDkey['filter'])  and  ($filter=array_shift($IDkey['filter']))!==NULL)  {
      $x=array_subkey($A, 1, $filter, $KeepKey);
      if (is_array($filter)  or  @substr($filter, 0, 1)=="/"  or  $KeepKey)  {
        $indxLevel++;  array_unshift($IDkey['filter'], NULL);  }
      if (is_iterateable($x)  and  $t=array_subkey($x, $indxLevel-1, $IDkey, $KeepTree))  $subA=$t;  }
    else  { $subA=array();
    if ($KeepTree)  {
      if ($KeepKey)
        foreach ($A as $indx => $data)  {
          if (is_iterateable($data)  and  NULL!==($t=array_subkey($data, $indxLevel-1, $IDkey, $KeepTree)))  $subA[$indx]=$t;  }
      else
        foreach ($A as $data)  {
          if (is_iterateable($data)  and  NULL!==($t=array_subkey($data, $indxLevel-1, $IDkey, $KeepTree)))  $subA[]=$t;  }  }
    else
        foreach ($A as $data)  {
          if (is_iterateable($data)  and  NULL!==($t=array_subkey($data, $indxLevel-1, $IDkey)))  {
            if (is_array($t)) $subA=array_merge($subA, $t);  else  $subA[]=$t;  }  }  }  }
  return ($subA  and  count($subA)) ? $subA : NULL;  }



$myarray=array(
  'John' => array(
    'IDinfo' => array(
      'name' => array('first' => "Johnathon", 'last' => "Smith"),
      'address' => array(
        'number' => "1234", 'street' => "10th Street", 'city' => "Anytown", 'state' => "NY", 'country' => "USA"),
      'birthday' => array('month' => "January", 'day' => "1", 'year' => "2000")),
    'friends' => array(
      'bipeds' => array(
        array('name' => 'Jenny', 'species' => "human", 'relation' => "romance", 'duration' => "1 year"),
        array('name' => 'Freddy', 'species' => "human", 'relation' => "tennis partner", 'duration' => "3 months"),
        array('name' => 'Tommy', 'species' => "subhuman", 'relation' => "drinking buddy", 'duration' => "7 years")),
      'quadropeds' => array(
        array('name' => 'Rover', 'species' => "canine", 'relation' => "best friend", 'duration' => "9 years"),
        array('name' => 'Felix', 'species' => "feline", 'relation' => "sunbathing companion", 'duration' => "7 years")),
      'octopeds' => array(
        array('name' => 'Boris', 'species' => "common spider", 'relation' => "insect manager", 'duration' => "4 months"))),
    'antagonists' => array(
      'bipeds' => array(
        array('name' => 'Mr. Dunbar', 'species' => "jerkass", 'relation' => "boss", 'duration' => "11 months"),
        array('name' => "Mr. Pendergrass", 'species' => "prickhead", 'relation' => "dog-owning neighbor", 'duration' => "2 years")),
      'quadropeds' => array(
        array('name' => "Max", 'species' => "canine", 'relation' => "yard crapping neighbor", 'duration' => "2 years"))),
    'job' => array('company' => "XYZ Shoes", 'title' => "stockboy", 'payrate' => "$8.95/hour")),

  'Jenny' => array(
    'IDinfo' => array(
      'name' => array('first' => "Jennifer", 'last' => "Gudentite"),
      'address' => array(
        'number' => "1269", 'street' => "Main Street", 'city' => "Anytown", 'state' => "", 'country' => "Germany"),
      'birthday' => array('month' => "February", 'day' => "29", 'year' => "2001")),
    'friends' => array(
      'bipeds' => array(
        array('name' => "Holly", 'species' => "human", 'relation' => "hairdresser", 'duration' => "5 years"),
        array('name' => "Maggie", 'species' => "human", 'relation' => "tennis partner", 'duration' => "2 years")),
      'quadropeds' => array(
        array('name' => "Fluffy", 'species' => "feline", 'relation' => "emotional management", 'duration' => "4 years"))),
    'antagonists' => array(
      'bipeds' => array(
        array('name' => "Roxy", 'species' => "bitch", 'relation' => "workplace slut", 'duration' => "1.5 years")),
      'octopeds' => array(
        array('name' => "Dripping Fang", 'species' => "wolf spider", 'relation' => "bathroom dweller", 'duration' => "1 week"))),
    'job' => array('company' => "Bras are us", 'title' => "salesmodel", 'payrate' => "$19.50/hour")),

  'Maggie' => array(
    'IDinfo' => array(
      'name' => array('first' => "Magatha", 'last' => "Kristie"),
      'address' => array(
        'number' => "1258", 'street' => "Hightower Rd.", 'city' => "Anytown", 'state' => "", 'country' => "England"),
      'birthday' => array('month' => "March", 'day' => "15", 'year' => "2002")),
    'friends' => array(
      'bipeds' => array(
        array('name' => "Jenny", 'species' => "human", 'relation' => "tennis partner", 'duration' => "2 years"),
        array('name' => "John", 'species' => "human", 'relation' => "untold satisfation", 'duration' => "10 months"),
        array('name' => "Onyx", 'species' => "blackbird", 'relation' => "dead-of-night serenades", 'duration' => "3 years")),
      'quadropeds' => array(
        array('name' => "Tigger", 'species' => "feline", 'relation' => "tabletop decor approval", 'duration' => "3 years"))),
    'antagonists' => array(
      'bipeds' => array(
        array('name' => "Mr. Banish", 'species' => "human", 'relation' => "bus-driving pet-policy enforceer", 'duration' => "2.5 years")),
      'omnipeds' => array(
        array('name' => "Zippy", 'species' => "centipede", 'relation' => "basement artroom invader", 'duration' => "2 years"))),
    'job' => array('company' => "Pies in the Sky", 'title' => "Pie Baker", 'payrate' => "$7.50/hour")) );

/*// for PHP <5.3
$myfilter=array('filter' => array(NULL, 'myfilterfunction'));

Function myfilterfunction($A)  { // echo "<pre>",var_dump($A),"</pre>\n";

	$mysubfilter=array('filter' => array(
		NULL,
		'/peds/',
		'/./',
//    'get_dogs' ));
		array('get_dogs', 'get_humans')  ));

	return array_subkey¤($A, 4, $mysubfilter);  }

 echo "<br><br><pre>",var_dump(array_subkey¤($myarray, 2, $myfilter, KEEP_KEY)),"</pre>\n";
 echo "<br><br><pre>",var_dump(array_subkey¤($myarray, 3, array('name', 'birthday'))),"</pre>\n";

Function get_humans($A, &$indx=FALSE) {// echo "<pre>",var_dump($A),"</pre>\n";
	if ($A['species']=="human") {$indx="name";  return $A['name'];} else return NULL;}
Function get_dogs($A, &$indx=FALSE) {// echo "<pre>",var_dump($A),"</pre>\n";
	if ($A['species']=="canine") {$indx="name";  return $A['name'];} else return NULL;}
Function get_spiders($A, &$indx=FALSE) {// echo "<pre>",var_dump($A),"</pre>\n";
	if (strpos($A['species'], "spider")!==FALSE) {$indx="name";  return $A['name'];} else return NULL;}
*/


// for PHP >=5.3
$myfilter_01=array('filter' => array(
 NULL,
 function ($A, &$indx)  { // echo "<pre>",var_dump($A),"</pre>\n";
//  $indx="human and canine acquaintances";
  $indx=NULL;
  $mysubfilter=array('filter' => array(
    NULL,
    '/peds/',
    '/./',    //similar to NULL
    array(
      function ($A)  { // echo "<pre>",var_dump($A),"</pre>\n";
        if ($A['species']=="human")  {return $A['name'];}  else  return NULL;  },
      function ($A)  { // echo "<pre>",var_dump($A),"</pre>\n";
        if ($A['species']=="canine")  {return $A['name'];}  else  return NULL;  }
    )  ));
  return array_subkey¤5•3($A, 4, $mysubfilter);  }  ));


$myfilter_02=array('filter' => array(
 NULL,
 function ($A, &$indx)  { $indx="names of spiders";  return array_subkey¤5•3($A, 4, array('filter' => array(
    NULL,
    '/peds/',
    NULL,
    function ($A, &$indx)  { // echo "<pre>",var_dump($A),"</pre>\n";
        if (strpos($A['species'], "spider")!==FALSE)  {$indx="name";  return $A['name'];}  else  return NULL;  }
    )), KEEP_KEY);  }  ));

// Care must be taken how your function accepts parameters in the context it is used.
// Note how the subfilter in filter_01 contains functions that don't require 2 parameters,
//  as the subfilter does not keep keys.
// Note how this below will fail in the "get spiders and all relations" example without
//   internal initialization of $indx, because that example is processed under "do not keep key" terms;
//   but $indx is needed for the filter_03 example ("friendly spiders")
$get_spiders=function ($A, &$indx=FALSE)  { // echo "<pre>",var_dump($A),"</pre>\n";
        if (strpos($A['species'], "spider")!==FALSE)  {$indx="name";  return $A['name']." the Spider!";}
        else  return NULL;  };


$myfilter_03=array('filter' => array(
 NULL,
 function ($A, &$indx)  {global $get_spiders; $indx="friendly spiders";
  return array_subkey¤5•3($A, 4, array('filter' => array(
    "friends",
    '/peds/',
    NULL,
    $get_spiders)), KEEP_KEY);  }  ));


$myfilter_04=array('filter' => array(
 NULL,
 function ($A, &$indx)  { $indx="two-legged friends";  return array_subkey¤5•3($A, 4, array('filter' => array(
    "friends",
    '/ipeds/',
    NULL,
    NULL)), KEEP_TREE);  }  ));

$myfilter_05=array('filter' => array(
 NULL,
 function ($A) {return array_subkey¤5•3($A, 2, array('name', 'birthday'));}  ));

$myfilter_06=array('filter' => array(
 NULL,
 function ($A) {return array_subkey¤5•3(array_subkey¤5•3($A, 2, array('name', 'birthday')), 2, NULL);}  ));

$myfilter_07=array("re-index" => array('referred to as' => "name", 'biogenetic makeup' => "species"));

$myfilter_08=function ($A, &$indx)  { $indx="acquaintances"; return array_subkey¤5•3($A, 3,
    function ($A) { global $myfilter_07;  return array_subkey¤5•3($A, 2, $myfilter_07, KEEP_KEY); });  };

$myfilter_09=function ($A, &$indx)  { $indx=NULL; return array_subkey¤5•3($A, 3,
    function ($A) { global $myfilter_07;  return array_subkey¤5•3($A, 2, $myfilter_07, KEEP_KEY); });  };


  echo "Hello AGAIN!";
 echo "<h1>filter 01</h1><pre>",var_dump(array_subkey¤5•3($myarray, 2, $myfilter_01, KEEP_KEY)),"</pre>\n";
 echo "<hr><h1>filter 02</h1><pre>",var_dump(array_subkey¤5•3($myarray, 2, $myfilter_02, KEEP_KEY)),"</pre>\n";
 echo "<hr><h1>filter 03</h1><pre>",var_dump(array_subkey¤5•3($myarray, 2, $myfilter_03, KEEP_KEY)),"</pre>\n";
 echo "<hr><h1>get spiders</h1><pre>",var_dump(array_subkey¤5•3($myarray, 5, $get_spiders)),"</pre>\n";
 echo "<hr><h1>get spiders and all relations</h1><pre>",var_dump(array_subkey¤5•3($myarray, 5, array($get_spiders, "relation"))),"</pre>\n";
 echo "<hr><h1>filter 04</h1><pre>",var_dump(array_subkey¤5•3($myarray, 2, $myfilter_04, KEEP_KEY)),"</pre>\n";
 echo "<hr><h1>filter 05</h1><pre>",var_dump(array_subkey¤5•3($myarray, 2, $myfilter_05, KEEP_TREE)),"</pre>\n";
 echo "<hr><h1>filter 06</h1><pre>",var_dump(array_subkey¤5•3($myarray, 2, $myfilter_06, KEEP_TREE)),"</pre>\n";
 echo "<hr><br><pre>",var_dump(array_subkey¤5•3($myarray, 3, array('name', 'birthday'))),"</pre>\n";
 echo "<hr><br><pre>",var_dump(array_subkey¤5•3($myarray, 2, "job", KEEP_KEY)),"</pre>\n";
 echo "<hr><h1>level 3, name, keep key</h1><pre>",var_dump(array_subkey¤5•3($myarray, 3, "name", KEEP_KEY)),"</pre>\n";
 echo "<hr><h1>level 3, name, keep tree</h1><pre>",var_dump(array_subkey¤5•3($myarray, 3, "name", KEEP_TREE)),"</pre>\n";
 echo "<hr><h1>level 5, name, keep key</h1><pre>",var_dump(array_subkey¤5•3($myarray, 5, "name", KEEP_KEY)),"</pre>\n";
 echo "<hr><h1>level 5, name, keep tree</h1><pre>",var_dump(array_subkey¤5•3($myarray, 5, "name", KEEP_TREE)),"</pre>\n";
 echo "<hr><h1>filter 07 without keep_key</h1><pre>",var_dump(array_subkey¤5•3($myarray, 5, $myfilter_07)),"</pre>\n";
 echo "<hr><h1>filter 07 with keep_key</h1><pre>",var_dump(array_subkey¤5•3($myarray, 5, $myfilter_07, KEEP_KEY)),"</pre>\n";
 echo "<hr><h1>filter 08</h1><pre>",var_dump(array_subkey¤5•3($myarray, 2, $myfilter_08, KEEP_KEY)),"</pre>\n";
 echo "<hr><h1>filter 09</h1><pre>",var_dump(array_subkey¤5•3($myarray, 2, $myfilter_09, KEEP_KEY)),"</pre>\n";

// ===================================

Define ("MARKUP_RATE", 5.7);
Define ("SALES_TAX_RATE", 1.07);

Class SimpleInventory  {
  public $item;
  public $wholesale;
  public $taxPaid;
  function __construct($item, $wholesale, $taxPaid)  {
    $this->item=$item;
    $this->wholesale=$wholesale;
    $this->taxPaid=$taxPaid;  }  }

Class DomesticItem Extends SimpleInventory {
  private $markup_rate=1.79;
  public function price()  {return ($this->wholesale+$this->taxPaid)*$this->markup_rate*SALES_TAX_RATE;}  }


Class ImportItem Extends SimpleInventory {
  private $markup_rate=5.7;
  public $exchangeRate;
  function __construct($item, $wholesale, $taxPaid, $exchangeRate)  {
    $this->item=$item;
    $this->wholesale=$wholesale;
    $this->taxPaid=$taxPaid;
    $this->exchangeRate=$exchangeRate;  }
  public function price()  {
    return ($this->wholesale*$this->exchangeRate+$this->taxPaid)*$this->markup_rate*SALES_TAX_RATE;  }  }

$myInventory=array(
  'domestics' => array (
    'New Mexico' => array(
      new DomesticItem("leather saddle", 480, 33.60)),
    'Minnesoda' => array(
      new DomesticItem("wool hat", 22, 0),
      new DomesticItem("wool gloves", 25, 0)),
    'California' => array(
      new DomesticItem("guitar", 389, 35.01))),
  'imports' => array (
    'SouthAmerica' => array (
      'Argentina' => array(
        new ImportItem("leather saddle", 60, 5.79, .89),
        new ImportItem("leather bag", 10, .25, .89)),
      'Peru' => array(
        new ImportItem("wool hat", 2, 0, .82),
        new ImportItem("wool gloves", 2, 0, .82))),
    'CentralAmerica' => array(
      'Mexico' => array(
        new ImportItem("guitar", 280, 10.79, .37)),
      'Guatamala' => array(
        new ImportItem("woven belt", 7, 0, .32)))));

$myfilter_20=function($A, &$indx=FALSE)  { $indx=NULL;
  $filter_20f=function($A, &$indx=FALSE) {$indx=NULL;  return array('item' => $A->item, 'price' => $A->price());};
  return (is_array($A)) ?
    array_subkey¤5•3($A, 2, $filter_20f) :
    $filter_20f($A);  };

$myfilter_21=function($A, &$indx=FALSE)  { $indx=NULL;
  $filter_21f=function($A, &$indx=FALSE) {$indx=NULL;  return array(array('item' => $A->item, 'price' => $A->price()));};
  return (is_array($A)) ?
    array_subkey¤5•3($A, 2, $filter_21f) :
    $filter_21f($A);  };


 echo "<hr><hr><h1>Class based examples - the power grows!</h1><hr><hr>";
 echo "<pre>",var_dump(array_subkey¤5•3($myInventory, 5,
    function ($A, &$indx) {$indx="price";  return $A->price();}, KEEP_KEY)),"</pre>\n";
 echo "<hr><br><pre>",var_dump(array_subkey¤5•3($myInventory, 4,
    function ($A, &$indx) { $indx="price";
      return (is_object($A) and !is_callable($A)) ? $A->price() : NULL;  }, KEEP_KEY)),"</pre>\n";
 echo "<hr><h1>filter 20 with keep_key</h1><pre>",var_dump(array_subkey¤5•3($myInventory, 4, $myfilter_20, KEEP_KEY)),"</pre>\n";
 echo "<hr><h1>filter 20 without keep_key</h1><pre>",var_dump(array_subkey¤5•3($myInventory, 4, $myfilter_20)),"</pre>\n";
 echo "<hr><h1>filter 21 with keep_key</h1><pre>",var_dump(array_subkey¤5•3($myInventory, 4, $myfilter_21, KEEP_KEY)),"</pre>\n";
 echo "<hr><h1>filter 21 without keep_key</h1><pre>",var_dump(array_subkey¤5•3($myInventory, 4, $myfilter_21)),"</pre>\n";



?>