Login   Register  
PHP Classes
elePHPant
Icontem

File: Spyc.class.php

Recommend this page to a friend!
Stumble It! Stumble It! Bookmark in del.icio.us Bookmark in del.icio.us
  Classes of Tom Schaefer  >  QXML  >  Spyc.class.php  >  Download  
File: Spyc.class.php
Role: Auxiliary script
Content type: text/plain
Description: SPYC - A Simple PHP YAML Class
Class: QXML
XML <> Array (preserve attrib.), JSON, etc.
Author: By
Last change:
Date: 6 years ago
Size: 28,131 bytes
 

Contents

Class file image Download
<?php
  /** 
   * Spyc -- A Simple PHP YAML Class
   * @version 0.2.3 -- 2006-02-04
   * @author Chris Wanstrath <chris@ozmm.org>
   * @link http://spyc.sourceforge.net/
   * @copyright Copyright 2005-2006 Chris Wanstrath
   * @license http://www.opensource.org/licenses/mit-license.php MIT License
   * @package Spyc
   */

/** 
 * A node, used by Spyc for parsing YAML.
 * @package Spyc
 */
class YAMLNode
{
  public $parent;
  public $id;
  public $data;
  public $indent;
  public $children = false;

  static protected $lastNodeId = 0;

  /**
   * The constructor assigns the node a unique ID.
   *
   * @return void
   */
  public function __construct()
  {
    $this->id = ++self::$lastNodeId;
  }
}

/**
 * The Simple PHP YAML Class.
 *
 * This class can be used to read a YAML file and convert its contents
 * into a PHP array.  It currently supports a very limited subsection of
 * the YAML spec.
 *
 * Usage:
 * <code>
 *   $parser = new Spyc;
 *   $array  = $parser->load($file);
 * </code>
 * @package Spyc
 */
class Spyc
{
  /**
   * Load YAML into a PHP array statically
   *
   * The load method, when supplied with a YAML stream (string or file), 
   * will do its best to convert YAML in a file into a PHP array.  Pretty 
   * simple.
   *  Usage: 
   *  <code>
   *   $array = Spyc::YAMLLoad('lucky.yml');
   *   print_r($array);
   *  </code>
   *
   * @return array
   * @param string $input Path of YAML file or string containing YAML
   */
  public static function YAMLLoad($input)
  {
    $spyc = new Spyc();

    return $spyc->load($input);
  }

  /**
   * Dump YAML from PHP array statically
   *
   * The dump method, when supplied with an array, will do its best
   * to convert the array into friendly YAML.  Pretty simple.  Feel free to
   * save the returned string as nothing.yml and pass it around.
   *
   * Oh, and you can decide how big the indent is and what the wordwrap
   * for folding is.  Pretty cool -- just pass in 'false' for either if 
   * you want to use the default.
   *
   * Indent's default is 2 spaces, wordwrap's default is 40 characters.  And
   * you can turn off wordwrap by passing in 0.
   *
   * @return string
   * @param array $array PHP array
   * @param int $indent Pass in false to use the default, which is 2 
   * @param int $wordwrap Pass in 0 for no wordwrap, false for default (40)
   */
  public static function YAMLDump($array, $indent = false, $wordwrap = false)
  {
    $spyc = new Spyc();

    return $spyc->dump($array, $indent, $wordwrap);
  }

  /**
   * Load YAML into a PHP array from an instantiated object
   *
   * The load method, when supplied with a YAML stream (string or file path), 
   * will do its best to convert the YAML into a PHP array.  Pretty simple.
   *  Usage: 
   *  <code>
   *   $parser = new Spyc;
   *   $array  = $parser->load('lucky.yml');
   *   print_r($array);
   *  </code>
   *
   * @return array
   * @param string $input Path of YAML file or string containing YAML
   */
  public function load($input)
  {
    // See what type of input we're talking about
    // If it's not a file, assume it's a string
    if (!empty($input) && (strpos($input, "\n") === false) && file_exists($input))
    {
      $file = $input;
      $yaml = file($input);
    }
    else
    {
      $file = null;
      $yaml = explode("\n", $input);
    }

    // Initiate some objects and values
    $base              = new YAMLNode();
    $base->indent      = 0;
    $this->_lastIndent = 0;
    $this->_lastNode   = $base->id;
    $this->_inBlock    = false;
    $this->_isInline   = false;

    foreach ($yaml as $linenum => $line)
    {
      $ifchk = trim($line);

      // If the line starts with a tab (instead of a space), throw a fit.
      if (preg_match('/^ *(\t) *+(\w+)/', $line))
      {
        $error = sprintf('ERROR: %sLine %d in your input YAML begins with a tab. YAML only recognizes spaces. Please reformat.', ($file ? "File $file " : ''), $linenum + 1);

        throw new Exception($error);
      }

      if ($this->_inBlock === false && empty($ifchk))
      {
        continue;
      }
      else if ($this->_inBlock == true && empty($ifchk))
      {
        $last =& $this->_allNodes[$this->_lastNode];
        $last->data[key($last->data)] .= "\n";
      }
      else if ($ifchk{0} != '#' && substr($ifchk, 0, 3) != '---')
      {
        // Create a new node and get its indent
        $node         = new YAMLNode();
        $node->indent = $this->_getIndent($line);

        // Check where the node lies in the hierarchy
        if ($this->_lastIndent == $node->indent)
        {
          // If we're in a block, add the text to the parent's data
          if ($this->_inBlock === true)
          {
            $parent =& $this->_allNodes[$this->_lastNode];
            $parent->data[key($parent->data)] .= trim($line).$this->_blockEnd;
          }
          else
          {
            // The current node's parent is the same as the previous node's
            if (isset($this->_allNodes[$this->_lastNode]))
            {
              $node->parent = $this->_allNodes[$this->_lastNode]->parent;
            }
          }
        }
        else if ($this->_lastIndent < $node->indent)
        {
          if ($this->_inBlock === true)
          {
            $parent =& $this->_allNodes[$this->_lastNode];
            $parent->data[key($parent->data)] .= substr($line, $this->_lastIndent).$this->_blockEnd;
          }
          else if ($this->_inBlock === false)
          {
            // The current node's parent is the previous node
            $node->parent = $this->_lastNode;

            // If the value of the last node's data was > or | we need to
            // start blocking i.e. taking in all lines as a text value until
            // we drop our indent.
            $parent =& $this->_allNodes[$node->parent];
            $this->_allNodes[$node->parent]->children = true;
            if (is_array($parent->data))
            {
              $chk = $parent->data[key($parent->data)];
              if ($chk === '>')
              {
                $this->_inBlock  = true;
                $this->_blockEnd = ' ';
                $parent->data[key($parent->data)] = str_replace('>', '', $parent->data[key($parent->data)]);
                $parent->data[key($parent->data)] .= trim($line).' ';
                $this->_allNodes[$node->parent]->children = false;
                $this->_lastIndent = $node->indent;
              }
              else if ($chk === '|')
              {
                $this->_inBlock  = true;
                $this->_blockEnd = "\n";
                $parent->data[key($parent->data)] = str_replace('|', '', $parent->data[key($parent->data)]);
                $parent->data[key($parent->data)] .= trim($line)."\n";
                $this->_allNodes[$node->parent]->children = false;
                $this->_lastIndent = $node->indent;
              }
            }
          }
        }
        else if ($this->_lastIndent > $node->indent)
        {
          // Any block we had going is dead now
          if ($this->_inBlock === true)
          {
            $this->_inBlock = false;
            if ($this->_blockEnd = "\n")
            {
              $last =& $this->_allNodes[$this->_lastNode];
              $last->data[key($last->data)] = trim($last->data[key($last->data)]);
            }
          }

          // We don't know the parent of the node so we have to find it
          // foreach ($this->_allNodes as $n) {
          foreach ($this->_indentSort[$node->indent] as $n)
          {
            if ($n->indent == $node->indent)
            {
              $node->parent = $n->parent;
            }
          }
        }

        if ($this->_inBlock === false)
        {
          // Set these properties with information from our current node
          $this->_lastIndent = $node->indent;
          // Set the last node
          $this->_lastNode = $node->id;
          // Parse the YAML line and return its data
          $node->data = $this->_parseLine($line);
          // Add the node to the master list
          $this->_allNodes[$node->id] = $node;
          // Add a reference to the node in an indent array
          $this->_indentSort[$node->indent][] =& $this->_allNodes[$node->id];
          // Add a reference to the node in a References array if this node
          // has a YAML reference in it.
          if ( 
             ((is_array($node->data)) &&
              isset($node->data[key($node->data)]) &&
              (!is_array($node->data[key($node->data)])))
            &&
             ((preg_match('/^&([^ ]+)/', $node->data[key($node->data)]))
              || 
              (preg_match('/^\*([^ ]+)/', $node->data[key($node->data)])))
          )
          {
            $this->_haveRefs[] =& $this->_allNodes[$node->id];
          }
          else if (
            ((is_array($node->data)) &&
             isset($node->data[key($node->data)]) &&
             (is_array($node->data[key($node->data)])))
          )
          {
            // Incomplete reference making code.  Ugly, needs cleaned up.
            foreach ($node->data[key($node->data)] as $d)
            {
              if (!is_array($d) && ((preg_match('/^&([^ ]+)/', $d)) || (preg_match('/^\*([^ ]+)/', $d))))
              {
                $this->_haveRefs[] =& $this->_allNodes[$node->id];
              }
            }
          }
        }
      }
    }
    unset($node);

    // Here we travel through node-space and pick out references (& and *)
    $this->_linkReferences();

    // Build the PHP array out of node-space
    $trunk = $this->_buildArray();

    return $trunk;
  }

  /**
   * Dump PHP array to YAML
   *
   * The dump method, when supplied with an array, will do its best
   * to convert the array into friendly YAML.  Pretty simple.  Feel free to
   * save the returned string as tasteful.yml and pass it around.
   *
   * Oh, and you can decide how big the indent is and what the wordwrap
   * for folding is.  Pretty cool -- just pass in 'false' for either if 
   * you want to use the default.
   *
   * Indent's default is 2 spaces, wordwrap's default is 40 characters.  And
   * you can turn off wordwrap by passing in 0.
   *
   * @return string
   * @param array $array PHP array
   * @param int $indent Pass in false to use the default, which is 2 
   * @param int $wordwrap Pass in 0 for no wordwrap, false for default (40)
   */
   public function dump($array, $indent = false, $wordwrap = false)
   {
    // Dumps to some very clean YAML.  We'll have to add some more features
    // and options soon.  And better support for folding.

    // New features and options.
    if ($indent === false or !is_numeric($indent))
    {
      $this->_dumpIndent = 2;
    }
    else
    {
      $this->_dumpIndent = $indent;
    }

    if ($wordwrap === false or !is_numeric($wordwrap))
    {
      $this->_dumpWordWrap = 40;
    }
    else
    {
      $this->_dumpWordWrap = $wordwrap;
    }

    // New YAML document
    $string = "---\n";

    // Start at the base of the array and move through it.
    foreach ($array as $key => $value)
    {
      $string .= $this->_yamlize($key, $value, 0);
    }

    return $string;
  }

  protected $_haveRefs;
  protected $_allNodes;
  protected $_lastIndent;
  protected $_lastNode;
  protected $_inBlock;
  protected $_isInline;
  protected $_dumpIndent;
  protected $_dumpWordWrap;

  /**
   * Attempts to convert a key / value array item to YAML
   *
   * @return string
   * @param $key The name of the key
   * @param $value The value of the item
   * @param $indent The indent of the current node
   */
   protected function _yamlize($key, $value, $indent)
   {
    if (is_array($value))
    {
      // It has children.  What to do?
      // Make it the right kind of item
      $string = $this->_dumpNode($key, null, $indent);
      // Add the indent
      $indent += $this->_dumpIndent;
      // Yamlize the array
      $string .= $this->_yamlizeArray($value, $indent);
    }
    else if (!is_array($value))
    {
      // It doesn't have children.  Yip.
      $string = $this->_dumpNode($key, $value, $indent);
    }

    return $string;
  }

  /**
   * Attempts to convert an array to YAML
   *
   * @return string
   * @param $array The array you want to convert
   * @param $indent The indent of the current level
   */
   protected function _yamlizeArray($array, $indent)
   {
    if (is_array($array))
    {
      $string = '';
      foreach ($array as $key => $value)
      {
        $string .= $this->_yamlize($key, $value, $indent);
      }

      return $string;
    }
    else
    {
      return false;
    }
  }

  /**
   * Returns YAML from a key and a value
   *
   * @return string
   * @param $key The name of the key
   * @param $value The value of the item
   * @param $indent The indent of the current node
   */
   protected function _dumpNode($key, $value, $indent)
   {
    if (is_object($value))
    {
       if (method_exists($value, '__toString'))
       {
         $value = (string) $value;
       }
       else
       {
         $ref = new ReflectionObject($value);
         $value = $ref->getName();
       }
    }

    // do some folding here, for blocks
    if (strpos($value,"\n"))
    {
      $value = $this->_doLiteralBlock($value, $indent);
    }
    else
    {
      $value  = $this->_doFolding($value, $indent);
    }

    $spaces = str_repeat(' ', $indent);

    if (is_int($key))
    {
      // It's a sequence
      $string = $spaces.'- '.$value."\n";
    }
    else
    {
      // It's mapped
      $string = $spaces.$key.': '.$value."\n";
    }

    return $string;
  }

  /**
   * Creates a literal block for dumping
   *
   * @return string
   * @param $value 
   * @param $indent int The value of the indent
   */ 
   protected function _doLiteralBlock($value, $indent)
   {
    $exploded = explode("\n", $value);
    $newValue = '|';
    $indent  += $this->_dumpIndent;
    $spaces   = str_repeat(' ', $indent);
    foreach ($exploded as $line)
    {
      $newValue .= "\n".$spaces.trim($line);
    }
    return $newValue;
  }

  /**
   * Folds a string of text, if necessary
   *
   * @return string
   * @param $value The string you wish to fold
   */
   protected function _doFolding($value, $indent)
   {
    // Don't do anything if wordwrap is set to 0
    if ($this->_dumpWordWrap === 0)
    {
      return $value;
    }

    if (strlen($value) > $this->_dumpWordWrap)
    {
      $indent += $this->_dumpIndent;
      $indent = str_repeat(' ', $indent);
      $wrapped = wordwrap($value, $this->_dumpWordWrap, "\n$indent");
      $value   = ">\n".$indent.$wrapped;
    }

    return $value;
  }

  /* Methods used in loading */

  /**
   * Finds and returns the indentation of a YAML line
   *
   * @return int
   * @param string $line A line from the YAML file
   */
   protected function _getIndent($line)
   {
    preg_match('/^\s{1,}/', $line, $match);
    if (!empty($match[0]))
    {
      $indent = substr_count($match[0], ' ');
    }
    else
    {
      $indent = 0;
    }

    return $indent;
  }

  /**
   * Parses YAML code and returns an array for a node
   *
   * @return array
   * @param string $line A line from the YAML file
   */
  protected function _parseLine($line)
  {
    $line = trim($line);

    $array = array();

    if (preg_match('/^-(.*):$/', $line))
    {
      // It's a mapped sequence
      $key         = trim(substr(substr($line,1), 0, -1));
      $array[$key] = '';
    }
    else if ($line[0] == '-' && substr($line, 0, 3) != '---')
    {
      // It's a list item but not a new stream
      if (strlen($line) > 1)
      {
        $value   = trim(substr($line, 1));
        // Set the type of the value.  Int, string, etc
        $value   = $this->_toType($value);
        $array[] = $value;
      }
      else
      {
        $array[] = array();
      }
    }
    else if (preg_match('/^(.+):/', $line, $key))
    {
      // It's a key/value pair most likely
      // If the key is in double quotes pull it out
      if (preg_match('/^(["\'](.*)["\'](\s)*:)/', $line, $matches))
      {
        $value = trim(str_replace($matches[1], '', $line));
        $key   = $matches[2];
      }
      else
      {
        // Do some guesswork as to the key and the value
        $explode = explode(':', $line);
        $key     = trim($explode[0]);
        array_shift($explode);
        $value   = trim(implode(':', $explode));
      }

      // Set the type of the value.  Int, string, etc
      $value = $this->_toType($value);
      if (empty($key))
      {
        $array[]     = $value;
      }
      else
      {
        $array[$key] = $value;
      }
    }

    return $array;
  }

  /**
   * Finds the type of the passed value, returns the value as the new type.
   *
   * @param string $value
   * @return mixed
   */
  protected function _toType($value)
  {
    $value = trim($value);
    if ($value && !('"' == $value[0] || "'" == $value[0]))
    {
      $value = preg_replace('/\s*#(.+)$/', '', $value);
    }

    if (preg_match('/^("(.*)"|\'(.*)\')/', $value, $matches))
    {
      $value = (string) preg_replace('/(\'\'|\\\\\')/', "'", end($matches));
      $value = preg_replace('/\\\\"/', '"', $value);
    }
    else if (preg_match('/^\\[\s*\\]$/', $value, $matches))
    {
      $value = array();
    }
    else if (preg_match('/^{}$/', $value, $matches))
    {
      $value = array();
    }
    else if (preg_match('/^\\[(.+)\\]$/', $value, $matches))
    {
      // Inline Sequence

      // Take out strings sequences and mappings
      $explode = $this->_inlineEscape($matches[1]);

      // Propogate value array
      $value  = array();
      foreach ($explode as $v)
      {
        $value[] = $this->_toType($v);
      }
    }
    else if (strpos($value,': ') !== false && !preg_match('/^{(.+)/', $value))
    {
        // It's a map
        $array = explode(': ', $value);
        $key   = trim($array[0]);
        array_shift($array);
        $value = trim(implode(': ', $array));
        $value = $this->_toType($value);
        $value = array($key => $value);
    }
    else if (preg_match("/{(.+)}$/", $value, $matches))
    {
      // Inline Mapping

      // Take out strings sequences and mappings
      $explode = $this->_inlineEscape($matches[1]);

      // Propogate value array
      $array = array();
      foreach ($explode as $v)
      {
        $array = $array + $this->_toType($v);
      }
      $value = $array;
    }
    else if (strtolower($value) == 'null' or $value == '' or $value == '~')
    {
      $value = null;
    }
    else if (ctype_digit($value))
    {
      $value = (int) $value;
    }
    else if (in_array(strtolower($value), array('true', 'on', '+', 'yes', 'y')))
    {
      $value = true;
    }
    else if (in_array(strtolower($value), array('false', 'off', '-', 'no', 'n')))
    {
      $value = false;
    }
    else if (is_numeric($value))
    {
      $value = (float) $value;
    }

    return $value;
  }

  /**
   * Used in inlines to check for more inlines or quoted strings
   *
   * @return array
   */
   protected function _inlineEscape($inline)
   {
    // There's gotta be a cleaner way to do this...
    // While pure sequences seem to be nesting just fine,
    // pure mappings and mappings with sequences inside can't go very
    // deep.  This needs to be fixed.

    // Check for strings
    $regex = '/(?:(")|(?:\'))((?(1)[^"]+|[^\']+))(?(1)"|\')/';
    if (preg_match_all($regex, $inline, $strings))
    {
      foreach ($strings[0] as $string)
      {
        $saved_strings[] = $string;
      }
      $inline  = preg_replace($regex, 'YAMLString', $inline);
    }
    unset($regex);

    // Check for sequences
    if (preg_match_all('/\[(.+)\]/U', $inline, $seqs))
    {
      $inline = preg_replace('/\[(.+)\]/U', 'YAMLSeq', $inline);
      $seqs   = $seqs[0];
    }

    // Check for mappings
    if (preg_match_all('/{(.+)}/U', $inline, $maps))
    {
      $inline = preg_replace('/{(.+)}/U', 'YAMLMap', $inline);
      $maps   = $maps[0];
    }

    $explode = explode(', ', $inline);

    // Re-add the strings
    if (!empty($saved_strings))
    {
      $i = 0;
      foreach ($explode as $key => $value)
      {
        if (false !== strpos($value,'YAMLString'))
        {
          $explode[$key] = str_replace('YAMLString', $saved_strings[$i], $value);
          ++$i;
        }
      }
    }

    // Re-add the sequences
    if (!empty($seqs))
    {
      $i = 0;
      foreach ($explode as $key => $value)
      {
        if (strpos($value,'YAMLSeq') !== false)
        {
          $explode[$key] = str_replace('YAMLSeq', $seqs[$i], $value);
          ++$i;
        }
      }
    }

    // Re-add the mappings
    if (!empty($maps))
    {
      $i = 0;
      foreach ($explode as $key => $value)
      {
        if (strpos($value,'YAMLMap') !== false)
        {
          $explode[$key] = str_replace('YAMLMap', $maps[$i], $value);
          ++$i;
        }
      }
    }

    return $explode;
  }

  /**
   * Builds the PHP array from all the YAML nodes we've gathered
   *
   * @return array
   */
   protected function _buildArray()
   {
    $trunk = array();

    if (!isset($this->_indentSort[0]))
    {
      return $trunk;
    }

    foreach ($this->_indentSort[0] as $n)
    {
      if (empty($n->parent))
      {
        $this->_nodeArrayizeData($n);
        // Check for references and copy the needed data to complete them.
        $this->_makeReferences($n);
        // Merge our data with the big array we're building
        $trunk = $this->_array_kmerge($trunk, $n->data);
      }
    }

    return $trunk;
  }

  /**
   * Traverses node-space and sets references (& and *) accordingly
   *
   * @return bool
   */
   protected function _linkReferences()
   {
    if (is_array($this->_haveRefs))
    {
      foreach ($this->_haveRefs as $node)
      {
        if (!empty($node->data))
        {
          $key = key($node->data);
          // If it's an array, don't check.
          if (is_array($node->data[$key]))
          {
            foreach ($node->data[$key] as $k => $v)
            {
              $this->_linkRef($node, $key, $k, $v);
            }
          }
          else
          {
            $this->_linkRef($node, $key);
          }
        }
      } 
    }

    return true;
  }

  function _linkRef(&$n, $key, $k = null, $v = null)
  {
    if (empty($k) && empty($v))
    {
      // Look for &refs
      if (preg_match('/^&([^ ]+)/', $n->data[$key], $matches))
      {
        // Flag the node so we know it's a reference
        $this->_allNodes[$n->id]->ref = substr($matches[0], 1);
        $this->_allNodes[$n->id]->data[$key] = substr($n->data[$key], strlen($matches[0]) + 1);
      // Look for *refs
      }
      else if (preg_match('/^\*([^ ]+)/', $n->data[$key], $matches))
      {
        $ref = substr($matches[0], 1);
        // Flag the node as having a reference
        $this->_allNodes[$n->id]->refKey = $ref;
      }
    }
    else if (!empty($k) && !empty($v))
    {
      if (preg_match('/^&([^ ]+)/', $v, $matches))
      {
        // Flag the node so we know it's a reference
        $this->_allNodes[$n->id]->ref = substr($matches[0], 1);
        $this->_allNodes[$n->id]->data[$key][$k] = substr($v, strlen($matches[0]) + 1);
      // Look for *refs
      }
      else if (preg_match('/^\*([^ ]+)/', $v, $matches))
      {
        $ref = substr($matches[0], 1);
        // Flag the node as having a reference
        $this->_allNodes[$n->id]->refKey =  $ref;
      }
    }
  }

  /**
   * Finds the children of a node and aids in the building of the PHP array
   *
   * @param int $nid The id of the node whose children we're gathering
   * @return array
   */
   protected function _gatherChildren($nid)
   {
    $return = array();
    $node   =& $this->_allNodes[$nid];
    foreach ($this->_allNodes as $z)
    {
      if ($z->parent == $node->id)
      {
        // We found a child
        $this->_nodeArrayizeData($z);
        // Check for references
        $this->_makeReferences($z);
        // Merge with the big array we're returning
        // The big array being all the data of the children of our parent node
        $return = $this->_array_kmerge($return, $z->data);
      }
    }

    return $return;
  }

  /**
   * Turns a node's data and its children's data into a PHP array
   *
   *
   * @param array $node The node which you want to arrayize
   * @return boolean
   */
   protected function _nodeArrayizeData(&$node)
   {
    if (is_array($node->data) && $node->children == true)
    {
      // This node has children, so we need to find them
      $childs = $this->_gatherChildren($node->id);
      // We've gathered all our children's data and are ready to use it
      $key = key($node->data);
      $key = empty($key) ? 0 : $key;
      // If it's an array, add to it of course
      if (is_array($node->data[$key]))
      {
        $node->data[$key] = $this->_array_kmerge($node->data[$key], $childs);
      }
      else
      {
        $node->data[$key] = $childs;
      }
    }
    else if (!is_array($node->data) && $node->children == true)
    {
      // Same as above, find the children of this node
      $childs       = $this->_gatherChildren($node->id);
      $node->data   = array();
      $node->data[] = $childs;
    }

    // We edited $node by reference, so just return true
    return true;
  }

  /**
   * Traverses node-space and copies references to / from this object.
   *
   * @param object $z A node whose references we wish to make real
   * @return bool
   */
   protected function _makeReferences(&$z)
   {
    // It is a reference
    if (isset($z->ref))
    {
      $key                = key($z->data);
      // Copy the data to this object for easy retrieval later
      $this->ref[$z->ref] =& $z->data[$key];
    // It has a reference
    }
    else if (isset($z->refKey))
    {
      if (isset($this->ref[$z->refKey]))
      {
        $key           = key($z->data);
        // Copy the data from this object to make the node a real reference
        $z->data[$key] =& $this->ref[$z->refKey];
      }
    }

    return true;
  }

  /**
   * Merges arrays and maintains numeric keys.
   *
   * An ever-so-slightly modified version of the array_kmerge() function posted
   * to php.net by mail at nospam dot iaindooley dot com on 2004-04-08.
   *
   * http://us3.php.net/manual/en/function.array-merge.php#41394
   *
   * @param array $arr1
   * @param array $arr2
   * @return array
   */
  protected function _array_kmerge($arr1, $arr2)
  {
    if (!is_array($arr1))
    {
      $arr1 = array();
    }
    if (!is_array($arr2))
    {
      $arr2 = array();
    }

    $keys  = array_merge(array_keys($arr1), array_keys($arr2));
    $vals  = array_merge(array_values($arr1), array_values($arr2));
    $ret   = array();
    foreach ($keys as $key)
    {
      list($unused, $val) = each($vals);
      if (isset($ret[$key]) && is_int($key))
      {
        $ret[] = $val;
      }
      else
      {
        $ret[$key] = $val;
      }
    }

    return $ret;
  }
}
?>