Advertisement
Pr4w

Spyc YAML Parser

Aug 24th, 2011
325
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
PHP 32.03 KB | None | 0 0
  1. <?php
  2. /**
  3.    * Spyc -- A Simple PHP YAML Class
  4.    * @version 0.5
  5.    * @author Vlad Andersen <vlad.andersen@gmail.com>
  6.    * @author Chris Wanstrath <chris@ozmm.org>
  7.    * @link http://code.google.com/p/spyc/
  8.    * @copyright Copyright 2005-2006 Chris Wanstrath, 2006-2011 Vlad Andersen
  9.    * @license http://www.opensource.org/licenses/mit-license.php MIT License
  10.    * @package Spyc
  11.    */
  12.  
  13. if (!function_exists('spyc_load')) {
  14.   /**
  15.    * Parses YAML to array.
  16.    * @param string $string YAML string.
  17.    * @return array
  18.    */
  19.   function spyc_load ($string) {
  20.     return Spyc::YAMLLoadString($string);
  21.   }
  22. }
  23.  
  24. if (!function_exists('spyc_load_file')) {
  25.   /**
  26.    * Parses YAML to array.
  27.    * @param string $file Path to YAML file.
  28.    * @return array
  29.    */
  30.   function spyc_load_file ($file) {
  31.     return Spyc::YAMLLoad($file);
  32.   }
  33. }
  34.  
  35. /**
  36.    * The Simple PHP YAML Class.
  37.    *
  38.    * This class can be used to read a YAML file and convert its contents
  39.    * into a PHP array.  It currently supports a very limited subsection of
  40.    * the YAML spec.
  41.    *
  42.    * Usage:
  43.    * <code>
  44.    *   $Spyc  = new Spyc;
  45.    *   $array = $Spyc->load($file);
  46.    * </code>
  47.    * or:
  48.    * <code>
  49.    *   $array = Spyc::YAMLLoad($file);
  50.    * </code>
  51.    * or:
  52.    * <code>
  53.    *   $array = spyc_load_file($file);
  54.    * </code>
  55.    * @package Spyc
  56.    */
  57. class Spyc {
  58.  
  59.   // SETTINGS
  60.  
  61.   const REMPTY = "\0\0\0\0\0";
  62.  
  63.   /**
  64.    * Setting this to true will force YAMLDump to enclose any string value in
  65.    * quotes.  False by default.
  66.    *
  67.    * @var bool
  68.    */
  69.   public $setting_dump_force_quotes = false;
  70.  
  71.   /**
  72.    * Setting this to true will forse YAMLLoad to use syck_load function when
  73.    * possible. False by default.
  74.    * @var bool
  75.    */
  76.   public $setting_use_syck_is_possible = false;
  77.  
  78.  
  79.  
  80.   /**#@+
  81.   * @access private
  82.   * @var mixed
  83.   */
  84.   private $_dumpIndent;
  85.   private $_dumpWordWrap;
  86.   private $_containsGroupAnchor = false;
  87.   private $_containsGroupAlias = false;
  88.   private $path;
  89.   private $result;
  90.   private $LiteralPlaceHolder = '___YAML_Literal_Block___';
  91.   private $SavedGroups = array();
  92.   private $indent;
  93.   /**
  94.    * Path modifier that should be applied after adding current element.
  95.    * @var array
  96.    */
  97.   private $delayedPath = array();
  98.  
  99.   /**#@+
  100.   * @access public
  101.   * @var mixed
  102.   */
  103.   public $_nodeId;
  104.  
  105. /**
  106.  * Load a valid YAML string to Spyc.
  107.  * @param string $input
  108.  * @return array
  109.  */
  110.   public function load ($input) {
  111.     return $this->__loadString($input);
  112.   }
  113.  
  114.  /**
  115.  * Load a valid YAML file to Spyc.
  116.  * @param string $file
  117.  * @return array
  118.  */
  119.   public function loadFile ($file) {
  120.     return $this->__load($file);
  121.   }
  122.  
  123.   /**
  124.      * Load YAML into a PHP array statically
  125.      *
  126.      * The load method, when supplied with a YAML stream (string or file),
  127.      * will do its best to convert YAML in a file into a PHP array.  Pretty
  128.      * simple.
  129.      *  Usage:
  130.      *  <code>
  131.      *   $array = Spyc::YAMLLoad('lucky.yaml');
  132.      *   print_r($array);
  133.      *  </code>
  134.      * @access public
  135.      * @return array
  136.      * @param string $input Path of YAML file or string containing YAML
  137.      */
  138.   public static function YAMLLoad($input) {
  139.     $Spyc = new Spyc;
  140.     return $Spyc->__load($input);
  141.   }
  142.  
  143.   /**
  144.      * Load a string of YAML into a PHP array statically
  145.      *
  146.      * The load method, when supplied with a YAML string, will do its best
  147.      * to convert YAML in a string into a PHP array.  Pretty simple.
  148.      *
  149.      * Note: use this function if you don't want files from the file system
  150.      * loaded and processed as YAML.  This is of interest to people concerned
  151.      * about security whose input is from a string.
  152.      *
  153.      *  Usage:
  154.      *  <code>
  155.      *   $array = Spyc::YAMLLoadString("---\n0: hello world\n");
  156.      *   print_r($array);
  157.      *  </code>
  158.      * @access public
  159.      * @return array
  160.      * @param string $input String containing YAML
  161.      */
  162.   public static function YAMLLoadString($input) {
  163.     $Spyc = new Spyc;
  164.     return $Spyc->__loadString($input);
  165.   }
  166.  
  167.   /**
  168.      * Dump YAML from PHP array statically
  169.      *
  170.      * The dump method, when supplied with an array, will do its best
  171.      * to convert the array into friendly YAML.  Pretty simple.  Feel free to
  172.      * save the returned string as nothing.yaml and pass it around.
  173.      *
  174.      * Oh, and you can decide how big the indent is and what the wordwrap
  175.      * for folding is.  Pretty cool -- just pass in 'false' for either if
  176.      * you want to use the default.
  177.      *
  178.      * Indent's default is 2 spaces, wordwrap's default is 40 characters.  And
  179.      * you can turn off wordwrap by passing in 0.
  180.      *
  181.      * @access public
  182.      * @return string
  183.      * @param array $array PHP array
  184.      * @param int $indent Pass in false to use the default, which is 2
  185.      * @param int $wordwrap Pass in 0 for no wordwrap, false for default (40)
  186.      */
  187.   public static function YAMLDump($array,$indent = false,$wordwrap = false) {
  188.     $spyc = new Spyc;
  189.     return $spyc->dump($array,$indent,$wordwrap);
  190.   }
  191.  
  192.  
  193.   /**
  194.      * Dump PHP array to YAML
  195.      *
  196.      * The dump method, when supplied with an array, will do its best
  197.      * to convert the array into friendly YAML.  Pretty simple.  Feel free to
  198.      * save the returned string as tasteful.yaml and pass it around.
  199.      *
  200.      * Oh, and you can decide how big the indent is and what the wordwrap
  201.      * for folding is.  Pretty cool -- just pass in 'false' for either if
  202.      * you want to use the default.
  203.      *
  204.      * Indent's default is 2 spaces, wordwrap's default is 40 characters.  And
  205.      * you can turn off wordwrap by passing in 0.
  206.      *
  207.      * @access public
  208.      * @return string
  209.      * @param array $array PHP array
  210.      * @param int $indent Pass in false to use the default, which is 2
  211.      * @param int $wordwrap Pass in 0 for no wordwrap, false for default (40)
  212.      */
  213.   public function dump($array,$indent = false,$wordwrap = false) {
  214.     // Dumps to some very clean YAML.  We'll have to add some more features
  215.     // and options soon.  And better support for folding.
  216.  
  217.     // New features and options.
  218.     if ($indent === false or !is_numeric($indent)) {
  219.       $this->_dumpIndent = 2;
  220.     } else {
  221.       $this->_dumpIndent = $indent;
  222.     }
  223.  
  224.     if ($wordwrap === false or !is_numeric($wordwrap)) {
  225.       $this->_dumpWordWrap = 40;
  226.     } else {
  227.       $this->_dumpWordWrap = $wordwrap;
  228.     }
  229.  
  230.     // New YAML document
  231.     $string = "---\n";
  232.  
  233.     // Start at the base of the array and move through it.
  234.     if ($array) {
  235.       $array = (array)$array;
  236.       $previous_key = -1;
  237.       foreach ($array as $key => $value) {
  238.         if (!isset($first_key)) $first_key = $key;
  239.         $string .= $this->_yamlize($key,$value,0,$previous_key, $first_key, $array);
  240.         $previous_key = $key;
  241.       }
  242.     }
  243.     return $string;
  244.   }
  245.  
  246.   /**
  247.      * Attempts to convert a key / value array item to YAML
  248.      * @access private
  249.      * @return string
  250.      * @param $key The name of the key
  251.      * @param $value The value of the item
  252.      * @param $indent The indent of the current node
  253.      */
  254.   private function _yamlize($key,$value,$indent, $previous_key = -1, $first_key = 0, $source_array = null) {
  255.     if (is_array($value)) {
  256.       if (empty ($value))
  257.         return $this->_dumpNode($key, array(), $indent, $previous_key, $first_key, $source_array);
  258.       // It has children.  What to do?
  259.       // Make it the right kind of item
  260.       $string = $this->_dumpNode($key, self::REMPTY, $indent, $previous_key, $first_key, $source_array);
  261.       // Add the indent
  262.       $indent += $this->_dumpIndent;
  263.       // Yamlize the array
  264.       $string .= $this->_yamlizeArray($value,$indent);
  265.     } elseif (!is_array($value)) {
  266.       // It doesn't have children.  Yip.
  267.       $string = $this->_dumpNode($key, $value, $indent, $previous_key, $first_key, $source_array);
  268.     }
  269.     return $string;
  270.   }
  271.  
  272.   /**
  273.      * Attempts to convert an array to YAML
  274.      * @access private
  275.      * @return string
  276.      * @param $array The array you want to convert
  277.      * @param $indent The indent of the current level
  278.      */
  279.   private function _yamlizeArray($array,$indent) {
  280.     if (is_array($array)) {
  281.       $string = '';
  282.       $previous_key = -1;
  283.       foreach ($array as $key => $value) {
  284.         if (!isset($first_key)) $first_key = $key;
  285.         $string .= $this->_yamlize($key, $value, $indent, $previous_key, $first_key, $array);
  286.         $previous_key = $key;
  287.       }
  288.       return $string;
  289.     } else {
  290.       return false;
  291.     }
  292.   }
  293.  
  294.   /**
  295.      * Returns YAML from a key and a value
  296.      * @access private
  297.      * @return string
  298.      * @param $key The name of the key
  299.      * @param $value The value of the item
  300.      * @param $indent The indent of the current node
  301.      */
  302.   private function _dumpNode($key, $value, $indent, $previous_key = -1, $first_key = 0, $source_array = null) {
  303.     // do some folding here, for blocks
  304.     if (is_string ($value) && ((strpos($value,"\n") !== false || strpos($value,": ") !== false || strpos($value,"- ") !== false ||
  305.       strpos($value,"*") !== false || strpos($value,"#") !== false || strpos($value,"<") !== false || strpos($value,">") !== false || strpos ($value, '  ') !== false ||
  306.       strpos($value,"[") !== false || strpos($value,"]") !== false || strpos($value,"{") !== false || strpos($value,"}") !== false) || strpos($value,"&") !== false || strpos($value, "'") !== false || strpos($value, "!") === 0 ||
  307.       substr ($value, -1, 1) == ':')
  308.     ) {
  309.       $value = $this->_doLiteralBlock($value,$indent);
  310.     } else {
  311.       $value  = $this->_doFolding($value,$indent);
  312.     }
  313.  
  314.     if ($value === array()) $value = '[ ]';
  315.     if (in_array ($value, array ('true', 'TRUE', 'false', 'FALSE', 'y', 'Y', 'n', 'N', 'null', 'NULL'), true)) {
  316.        $value = $this->_doLiteralBlock($value,$indent);
  317.     }
  318.     if (trim ($value) != $value)
  319.        $value = $this->_doLiteralBlock($value,$indent);
  320.  
  321.     if (is_bool($value)) {
  322.        $value = ($value) ? "true" : "false";
  323.     }
  324.    
  325.     if ($value === null) $value = 'null';
  326.     if ($value === "'" . self::REMPTY . "'") $value = null;
  327.  
  328.     $spaces = str_repeat(' ',$indent);
  329.  
  330.     //if (is_int($key) && $key - 1 == $previous_key && $first_key===0) {
  331.     if (is_array ($source_array) && array_keys($source_array) === range(0, count($source_array) - 1)) {
  332.       // It's a sequence
  333.       $string = $spaces.'- '.$value."\n";
  334.     } else {
  335.       // if ($first_key===0)  throw new Exception('Keys are all screwy.  The first one was zero, now it\'s "'. $key .'"');
  336.       // It's mapped
  337.       if (strpos($key, ":") !== false || strpos($key, "#") !== false) { $key = '"' . $key . '"'; }
  338.       $string = rtrim ($spaces.$key.': '.$value)."\n";
  339.     }
  340.     return $string;
  341.   }
  342.  
  343.   /**
  344.      * Creates a literal block for dumping
  345.      * @access private
  346.      * @return string
  347.      * @param $value
  348.      * @param $indent int The value of the indent
  349.      */
  350.   private function _doLiteralBlock($value,$indent) {
  351.     if ($value === "\n") return '\n';
  352.     if (strpos($value, "\n") === false && strpos($value, "'") === false) {
  353.       return sprintf ("'%s'", $value);
  354.     }
  355.     if (strpos($value, "\n") === false && strpos($value, '"') === false) {
  356.       return sprintf ('"%s"', $value);
  357.     }
  358.     $exploded = explode("\n",$value);
  359.     $newValue = '|';
  360.     $indent  += $this->_dumpIndent;
  361.     $spaces   = str_repeat(' ',$indent);
  362.     foreach ($exploded as $line) {
  363.       $newValue .= "\n" . $spaces . ($line);
  364.     }
  365.     return $newValue;
  366.   }
  367.  
  368.   /**
  369.      * Folds a string of text, if necessary
  370.      * @access private
  371.      * @return string
  372.      * @param $value The string you wish to fold
  373.      */
  374.   private function _doFolding($value,$indent) {
  375.     // Don't do anything if wordwrap is set to 0
  376.  
  377.     if ($this->_dumpWordWrap !== 0 && is_string ($value) && strlen($value) > $this->_dumpWordWrap) {
  378.       $indent += $this->_dumpIndent;
  379.       $indent = str_repeat(' ',$indent);
  380.       $wrapped = wordwrap($value,$this->_dumpWordWrap,"\n$indent");
  381.       $value   = ">\n".$indent.$wrapped;
  382.     } else {
  383.       if ($this->setting_dump_force_quotes && is_string ($value) && $value !== self::REMPTY)
  384.         $value = '"' . $value . '"';
  385.     }
  386.  
  387.  
  388.     return $value;
  389.   }
  390.  
  391. // LOADING FUNCTIONS
  392.  
  393.   private function __load($input) {
  394.     $Source = $this->loadFromSource($input);
  395.     return $this->loadWithSource($Source);
  396.   }
  397.  
  398.   private function __loadString($input) {
  399.     $Source = $this->loadFromString($input);
  400.     return $this->loadWithSource($Source);
  401.   }
  402.  
  403.   private function loadWithSource($Source) {
  404.     if (empty ($Source)) return array();
  405.     if ($this->setting_use_syck_is_possible && function_exists ('syck_load')) {
  406.       $array = syck_load (implode ('', $Source));
  407.       return is_array($array) ? $array : array();
  408.     }
  409.  
  410.     $this->path = array();
  411.     $this->result = array();
  412.  
  413.     $cnt = count($Source);
  414.     for ($i = 0; $i < $cnt; $i++) {
  415.       $line = $Source[$i];
  416.      
  417.       $this->indent = strlen($line) - strlen(ltrim($line));
  418.       $tempPath = $this->getParentPathByIndent($this->indent);
  419.       $line = self::stripIndent($line, $this->indent);
  420.       if (self::isComment($line)) continue;
  421.       if (self::isEmpty($line)) continue;
  422.       $this->path = $tempPath;
  423.  
  424.       $literalBlockStyle = self::startsLiteralBlock($line);
  425.       if ($literalBlockStyle) {
  426.         $line = rtrim ($line, $literalBlockStyle . " \n");
  427.         $literalBlock = '';
  428.         $line .= $this->LiteralPlaceHolder;
  429.         $literal_block_indent = strlen($Source[$i+1]) - strlen(ltrim($Source[$i+1]));
  430.         while (++$i < $cnt && $this->literalBlockContinues($Source[$i], $this->indent)) {
  431.           $literalBlock = $this->addLiteralLine($literalBlock, $Source[$i], $literalBlockStyle, $literal_block_indent);
  432.         }
  433.         $i--;
  434.       }
  435.  
  436.       while (++$i < $cnt && self::greedilyNeedNextLine($line)) {
  437.         $line = rtrim ($line, " \n\t\r") . ' ' . ltrim ($Source[$i], " \t");
  438.       }
  439.       $i--;
  440.  
  441.  
  442.  
  443.       if (strpos ($line, '#')) {
  444.         if (strpos ($line, '"') === false && strpos ($line, "'") === false)
  445.           $line = preg_replace('/\s+#(.+)$/','',$line);
  446.       }
  447.  
  448.       $lineArray = $this->_parseLine($line);
  449.  
  450.       if ($literalBlockStyle)
  451.         $lineArray = $this->revertLiteralPlaceHolder ($lineArray, $literalBlock);
  452.  
  453.       $this->addArray($lineArray, $this->indent);
  454.  
  455.       foreach ($this->delayedPath as $indent => $delayedPath)
  456.         $this->path[$indent] = $delayedPath;
  457.  
  458.       $this->delayedPath = array();
  459.  
  460.     }
  461.     return $this->result;
  462.   }
  463.  
  464.   private function loadFromSource ($input) {
  465.     if (!empty($input) && strpos($input, "\n") === false && file_exists($input))
  466.     return file($input);
  467.  
  468.     return $this->loadFromString($input);
  469.   }
  470.  
  471.   private function loadFromString ($input) {
  472.     $lines = explode("\n",$input);
  473.     foreach ($lines as $k => $_) {
  474.       $lines[$k] = rtrim ($_, "\r");
  475.     }
  476.     return $lines;
  477.   }
  478.  
  479.   /**
  480.      * Parses YAML code and returns an array for a node
  481.      * @access private
  482.      * @return array
  483.      * @param string $line A line from the YAML file
  484.      */
  485.   private function _parseLine($line) {
  486.     if (!$line) return array();
  487.     $line = trim($line);
  488.     if (!$line) return array();
  489.  
  490.     $array = array();
  491.  
  492.     $group = $this->nodeContainsGroup($line);
  493.     if ($group) {
  494.       $this->addGroup($line, $group);
  495.       $line = $this->stripGroup ($line, $group);
  496.     }
  497.  
  498.     if ($this->startsMappedSequence($line))
  499.       return $this->returnMappedSequence($line);
  500.  
  501.     if ($this->startsMappedValue($line))
  502.       return $this->returnMappedValue($line);
  503.  
  504.     if ($this->isArrayElement($line))
  505.      return $this->returnArrayElement($line);
  506.  
  507.     if ($this->isPlainArray($line))
  508.      return $this->returnPlainArray($line);
  509.      
  510.      
  511.     return $this->returnKeyValuePair($line);
  512.  
  513.   }
  514.  
  515.   /**
  516.      * Finds the type of the passed value, returns the value as the new type.
  517.      * @access private
  518.      * @param string $value
  519.      * @return mixed
  520.      */
  521.   private function _toType($value) {
  522.     if ($value === '') return null;
  523.     $first_character = $value[0];
  524.     $last_character = substr($value, -1, 1);
  525.  
  526.     $is_quoted = false;
  527.     do {
  528.       if (!$value) break;
  529.       if ($first_character != '"' && $first_character != "'") break;
  530.       if ($last_character != '"' && $last_character != "'") break;
  531.       $is_quoted = true;
  532.     } while (0);
  533.  
  534.     if ($is_quoted)
  535.       return strtr(substr ($value, 1, -1), array ('\\"' => '"', '\'\'' => '\'', '\\\'' => '\''));
  536.    
  537.     if (strpos($value, ' #') !== false && !$is_quoted)
  538.       $value = preg_replace('/\s+#(.+)$/','',$value);
  539.  
  540.     if (!$is_quoted) $value = str_replace('\n', "\n", $value);
  541.  
  542.     if ($first_character == '[' && $last_character == ']') {
  543.       // Take out strings sequences and mappings
  544.       $innerValue = trim(substr ($value, 1, -1));
  545.       if ($innerValue === '') return array();
  546.       $explode = $this->_inlineEscape($innerValue);
  547.       // Propagate value array
  548.       $value  = array();
  549.       foreach ($explode as $v) {
  550.         $value[] = $this->_toType($v);
  551.       }
  552.       return $value;
  553.     }
  554.  
  555.     if (strpos($value,': ')!==false && $first_character != '{') {
  556.       $array = explode(': ',$value);
  557.       $key   = trim($array[0]);
  558.       array_shift($array);
  559.       $value = trim(implode(': ',$array));
  560.       $value = $this->_toType($value);
  561.       return array($key => $value);
  562.     }
  563.    
  564.     if ($first_character == '{' && $last_character == '}') {
  565.       $innerValue = trim(substr ($value, 1, -1));
  566.       if ($innerValue === '') return array();
  567.       // Inline Mapping
  568.       // Take out strings sequences and mappings
  569.       $explode = $this->_inlineEscape($innerValue);
  570.       // Propagate value array
  571.       $array = array();
  572.       foreach ($explode as $v) {
  573.         $SubArr = $this->_toType($v);
  574.         if (empty($SubArr)) continue;
  575.         if (is_array ($SubArr)) {
  576.           $array[key($SubArr)] = $SubArr[key($SubArr)]; continue;
  577.         }
  578.         $array[] = $SubArr;
  579.       }
  580.       return $array;
  581.     }
  582.  
  583.     if ($value == 'null' || $value == 'NULL' || $value == 'Null' || $value == '' || $value == '~') {
  584.       return null;
  585.     }
  586.  
  587.     if ( is_numeric($value) && preg_match ('/^(-|)[1-9]+[0-9]*$/', $value) ){
  588.       $intvalue = (int)$value;
  589.       if ($intvalue != PHP_INT_MAX)
  590.         $value = $intvalue;
  591.       return $value;
  592.     }
  593.  
  594.     if (in_array($value,
  595.                  array('true', 'on', '+', 'yes', 'y', 'True', 'TRUE', 'On', 'ON', 'YES', 'Yes', 'Y'))) {
  596.       return true;
  597.     }
  598.  
  599.     if (in_array(strtolower($value),
  600.                  array('false', 'off', '-', 'no', 'n'))) {
  601.       return false;
  602.     }
  603.  
  604.     if (is_numeric($value)) {
  605.       if ($value === '0') return 0;
  606.       if (rtrim ($value, 0) === $value)
  607.         $value = (float)$value;
  608.       return $value;
  609.     }
  610.    
  611.     return $value;
  612.   }
  613.  
  614.   /**
  615.      * Used in inlines to check for more inlines or quoted strings
  616.      * @access private
  617.      * @return array
  618.      */
  619.   private function _inlineEscape($inline) {
  620.     // There's gotta be a cleaner way to do this...
  621.     // While pure sequences seem to be nesting just fine,
  622.     // pure mappings and mappings with sequences inside can't go very
  623.     // deep.  This needs to be fixed.
  624.  
  625.     $seqs = array();
  626.     $maps = array();
  627.     $saved_strings = array();
  628.  
  629.     // Check for strings
  630.     $regex = '/(?:(")|(?:\'))((?(1)[^"]+|[^\']+))(?(1)"|\')/';
  631.     if (preg_match_all($regex,$inline,$strings)) {
  632.       $saved_strings = $strings[0];
  633.       $inline  = preg_replace($regex,'YAMLString',$inline);
  634.     }
  635.     unset($regex);
  636.  
  637.     $i = 0;
  638.     do {
  639.  
  640.     // Check for sequences
  641.     while (preg_match('/\[([^{}\[\]]+)\]/U',$inline,$matchseqs)) {
  642.       $seqs[] = $matchseqs[0];
  643.       $inline = preg_replace('/\[([^{}\[\]]+)\]/U', ('YAMLSeq' . (count($seqs) - 1) . 's'), $inline, 1);
  644.     }
  645.  
  646.     // Check for mappings
  647.     while (preg_match('/{([^\[\]{}]+)}/U',$inline,$matchmaps)) {
  648.       $maps[] = $matchmaps[0];
  649.       $inline = preg_replace('/{([^\[\]{}]+)}/U', ('YAMLMap' . (count($maps) - 1) . 's'), $inline, 1);
  650.     }
  651.  
  652.     if ($i++ >= 10) break;
  653.  
  654.     } while (strpos ($inline, '[') !== false || strpos ($inline, '{') !== false);
  655.  
  656.     $explode = explode(', ',$inline);
  657.     $stringi = 0; $i = 0;
  658.  
  659.     while (1) {
  660.  
  661.     // Re-add the sequences
  662.     if (!empty($seqs)) {
  663.       foreach ($explode as $key => $value) {
  664.         if (strpos($value,'YAMLSeq') !== false) {
  665.           foreach ($seqs as $seqk => $seq) {
  666.             $explode[$key] = str_replace(('YAMLSeq'.$seqk.'s'),$seq,$value);
  667.             $value = $explode[$key];
  668.           }
  669.         }
  670.       }
  671.     }
  672.  
  673.     // Re-add the mappings
  674.     if (!empty($maps)) {
  675.       foreach ($explode as $key => $value) {
  676.         if (strpos($value,'YAMLMap') !== false) {
  677.           foreach ($maps as $mapk => $map) {
  678.             $explode[$key] = str_replace(('YAMLMap'.$mapk.'s'), $map, $value);
  679.             $value = $explode[$key];
  680.           }
  681.         }
  682.       }
  683.     }
  684.  
  685.  
  686.     // Re-add the strings
  687.     if (!empty($saved_strings)) {
  688.       foreach ($explode as $key => $value) {
  689.         while (strpos($value,'YAMLString') !== false) {
  690.           $explode[$key] = preg_replace('/YAMLString/',$saved_strings[$stringi],$value, 1);
  691.           unset($saved_strings[$stringi]);
  692.           ++$stringi;
  693.           $value = $explode[$key];
  694.         }
  695.       }
  696.     }
  697.  
  698.     $finished = true;
  699.     foreach ($explode as $key => $value) {
  700.       if (strpos($value,'YAMLSeq') !== false) {
  701.         $finished = false; break;
  702.       }
  703.       if (strpos($value,'YAMLMap') !== false) {
  704.         $finished = false; break;
  705.       }
  706.       if (strpos($value,'YAMLString') !== false) {
  707.         $finished = false; break;
  708.       }
  709.     }
  710.     if ($finished) break;
  711.  
  712.     $i++;
  713.     if ($i > 10)
  714.       break; // Prevent infinite loops.
  715.     }
  716.  
  717.     return $explode;
  718.   }
  719.  
  720.   private function literalBlockContinues ($line, $lineIndent) {
  721.     if (!trim($line)) return true;
  722.     if (strlen($line) - strlen(ltrim($line)) > $lineIndent) return true;
  723.     return false;
  724.   }
  725.  
  726.   private function referenceContentsByAlias ($alias) {
  727.     do {
  728.       if (!isset($this->SavedGroups[$alias])) { echo "Bad group name: $alias."; break; }
  729.       $groupPath = $this->SavedGroups[$alias];
  730.       $value = $this->result;
  731.       foreach ($groupPath as $k) {
  732.         $value = $value[$k];
  733.       }
  734.     } while (false);
  735.     return $value;
  736.   }
  737.  
  738.   private function addArrayInline ($array, $indent) {
  739.       $CommonGroupPath = $this->path;
  740.       if (empty ($array)) return false;
  741.      
  742.       foreach ($array as $k => $_) {
  743.         $this->addArray(array($k => $_), $indent);
  744.         $this->path = $CommonGroupPath;
  745.       }
  746.       return true;
  747.   }
  748.  
  749.   private function addArray ($incoming_data, $incoming_indent) {
  750.  
  751.    // print_r ($incoming_data);
  752.  
  753.     if (count ($incoming_data) > 1)
  754.       return $this->addArrayInline ($incoming_data, $incoming_indent);
  755.    
  756.     $key = key ($incoming_data);
  757.     $value = isset($incoming_data[$key]) ? $incoming_data[$key] : null;
  758.     if ($key === '__!YAMLZero') $key = '0';
  759.  
  760.     if ($incoming_indent == 0 && !$this->_containsGroupAlias && !$this->_containsGroupAnchor) { // Shortcut for root-level values.
  761.       if ($key || $key === '' || $key === '0') {
  762.         $this->result[$key] = $value;
  763.       } else {
  764.         $this->result[] = $value; end ($this->result); $key = key ($this->result);
  765.       }
  766.       $this->path[$incoming_indent] = $key;
  767.       return;
  768.     }
  769.  
  770.  
  771.    
  772.     $history = array();
  773.     // Unfolding inner array tree.
  774.     $history[] = $_arr = $this->result;
  775.     foreach ($this->path as $k) {
  776.       $history[] = $_arr = $_arr[$k];
  777.     }
  778.  
  779.     if ($this->_containsGroupAlias) {
  780.       $value = $this->referenceContentsByAlias($this->_containsGroupAlias);
  781.       $this->_containsGroupAlias = false;
  782.     }
  783.  
  784.  
  785.     // Adding string or numeric key to the innermost level or $this->arr.
  786.     if (is_string($key) && $key == '<<') {
  787.       if (!is_array ($_arr)) { $_arr = array (); }
  788.  
  789.       $_arr = array_merge ($_arr, $value);
  790.     } else if ($key || $key === '' || $key === '0') {
  791.       if (!is_array ($_arr))
  792.         $_arr = array ($key=>$value);
  793.       else
  794.         $_arr[$key] = $value;
  795.     } else {
  796.       if (!is_array ($_arr)) { $_arr = array ($value); $key = 0; }
  797.       else { $_arr[] = $value; end ($_arr); $key = key ($_arr); }
  798.     }
  799.  
  800.     $reverse_path = array_reverse($this->path);
  801.     $reverse_history = array_reverse ($history);
  802.     $reverse_history[0] = $_arr;
  803.     $cnt = count($reverse_history) - 1;
  804.     for ($i = 0; $i < $cnt; $i++) {
  805.       $reverse_history[$i+1][$reverse_path[$i]] = $reverse_history[$i];
  806.     }
  807.     $this->result = $reverse_history[$cnt];
  808.  
  809.     $this->path[$incoming_indent] = $key;
  810.  
  811.     if ($this->_containsGroupAnchor) {
  812.       $this->SavedGroups[$this->_containsGroupAnchor] = $this->path;
  813.       if (is_array ($value)) {
  814.         $k = key ($value);
  815.         if (!is_int ($k)) {
  816.           $this->SavedGroups[$this->_containsGroupAnchor][$incoming_indent + 2] = $k;
  817.         }
  818.       }
  819.       $this->_containsGroupAnchor = false;
  820.     }
  821.  
  822.   }
  823.  
  824.   private static function startsLiteralBlock ($line) {
  825.     $lastChar = substr (trim($line), -1);
  826.     if ($lastChar != '>' && $lastChar != '|') return false;
  827.     if ($lastChar == '|') return $lastChar;
  828.     // HTML tags should not be counted as literal blocks.
  829.     if (preg_match ('#<.*?>$#', $line)) return false;
  830.     return $lastChar;
  831.   }
  832.  
  833.   private static function greedilyNeedNextLine($line) {
  834.     $line = trim ($line);
  835.     if (!strlen($line)) return false;
  836.     if (substr ($line, -1, 1) == ']') return false;
  837.     if ($line[0] == '[') return true;
  838.     if (preg_match ('#^[^:]+?:\s*\[#', $line)) return true;
  839.     return false;
  840.   }
  841.  
  842.   private function addLiteralLine ($literalBlock, $line, $literalBlockStyle, $indent = -1) {
  843.     $line = self::stripIndent($line, $indent);
  844.     if ($literalBlockStyle !== '|') {
  845.         $line = self::stripIndent($line);
  846.     }
  847.     $line = rtrim ($line, "\r\n\t ") . "\n";
  848.     if ($literalBlockStyle == '|') {
  849.       return $literalBlock . $line;
  850.     }
  851.     if (strlen($line) == 0)
  852.       return rtrim($literalBlock, ' ') . "\n";
  853.     if ($line == "\n" && $literalBlockStyle == '>') {
  854.       return rtrim ($literalBlock, " \t") . "\n";
  855.     }
  856.     if ($line != "\n")
  857.       $line = trim ($line, "\r\n ") . " ";
  858.     return $literalBlock . $line;
  859.   }
  860.  
  861.    function revertLiteralPlaceHolder ($lineArray, $literalBlock) {
  862.      foreach ($lineArray as $k => $_) {
  863.       if (is_array($_))
  864.         $lineArray[$k] = $this->revertLiteralPlaceHolder ($_, $literalBlock);
  865.       else if (substr($_, -1 * strlen ($this->LiteralPlaceHolder)) == $this->LiteralPlaceHolder)
  866.            $lineArray[$k] = rtrim ($literalBlock, " \r\n");
  867.      }
  868.      return $lineArray;
  869.    }
  870.  
  871.   private static function stripIndent ($line, $indent = -1) {
  872.     if ($indent == -1) $indent = strlen($line) - strlen(ltrim($line));
  873.     return substr ($line, $indent);
  874.   }
  875.  
  876.   private function getParentPathByIndent ($indent) {
  877.     if ($indent == 0) return array();
  878.     $linePath = $this->path;
  879.     do {
  880.       end($linePath); $lastIndentInParentPath = key($linePath);
  881.       if ($indent <= $lastIndentInParentPath) array_pop ($linePath);
  882.     } while ($indent <= $lastIndentInParentPath);
  883.     return $linePath;
  884.   }
  885.  
  886.  
  887.   private function clearBiggerPathValues ($indent) {
  888.  
  889.  
  890.     if ($indent == 0) $this->path = array();
  891.     if (empty ($this->path)) return true;
  892.  
  893.     foreach ($this->path as $k => $_) {
  894.       if ($k > $indent) unset ($this->path[$k]);
  895.     }
  896.  
  897.     return true;
  898.   }
  899.  
  900.  
  901.   private static function isComment ($line) {
  902.     if (!$line) return false;
  903.     if ($line[0] == '#') return true;
  904.     if (trim($line, " \r\n\t") == '---') return true;
  905.     return false;
  906.   }
  907.  
  908.   private static function isEmpty ($line) {
  909.     return (trim ($line) === '');
  910.   }
  911.  
  912.  
  913.   private function isArrayElement ($line) {
  914.     if (!$line) return false;
  915.     if ($line[0] != '-') return false;
  916.     if (strlen ($line) > 3)
  917.       if (substr($line,0,3) == '---') return false;
  918.    
  919.     return true;
  920.   }
  921.  
  922.   private function isHashElement ($line) {
  923.     return strpos($line, ':');
  924.   }
  925.  
  926.   private function isLiteral ($line) {
  927.     if ($this->isArrayElement($line)) return false;
  928.     if ($this->isHashElement($line)) return false;
  929.     return true;
  930.   }
  931.  
  932.  
  933.   private static function unquote ($value) {
  934.     if (!$value) return $value;
  935.     if (!is_string($value)) return $value;
  936.     if ($value[0] == '\'') return trim ($value, '\'');
  937.     if ($value[0] == '"') return trim ($value, '"');
  938.     return $value;
  939.   }
  940.  
  941.   private function startsMappedSequence ($line) {
  942.     return ($line[0] == '-' && substr ($line, -1, 1) == ':');
  943.   }
  944.  
  945.   private function returnMappedSequence ($line) {
  946.     $array = array();
  947.     $key         = self::unquote(trim(substr($line,1,-1)));
  948.     $array[$key] = array();
  949.     $this->delayedPath = array(strpos ($line, $key) + $this->indent => $key);
  950.     return array($array);
  951.   }
  952.  
  953.   private function returnMappedValue ($line) {
  954.     $array = array();
  955.     $key         = self::unquote (trim(substr($line,0,-1)));
  956.     $array[$key] = '';
  957.     return $array;
  958.   }
  959.  
  960.   private function startsMappedValue ($line) {
  961.     return (substr ($line, -1, 1) == ':');
  962.   }
  963.  
  964.   private function isPlainArray ($line) {
  965.     return ($line[0] == '[' && substr ($line, -1, 1) == ']');
  966.   }
  967.  
  968.   private function returnPlainArray ($line) {
  969.     return $this->_toType($line);
  970.   }  
  971.  
  972.   private function returnKeyValuePair ($line) {
  973.     $array = array();
  974.     $key = '';
  975.     if (strpos ($line, ':')) {
  976.       // It's a key/value pair most likely
  977.       // If the key is in double quotes pull it out
  978.       if (($line[0] == '"' || $line[0] == "'") && preg_match('/^(["\'](.*)["\'](\s)*:)/',$line,$matches)) {
  979.         $value = trim(str_replace($matches[1],'',$line));
  980.         $key   = $matches[2];
  981.       } else {
  982.         // Do some guesswork as to the key and the value
  983.         $explode = explode(':',$line);
  984.         $key     = trim($explode[0]);
  985.         array_shift($explode);
  986.         $value   = trim(implode(':',$explode));
  987.       }
  988.       // Set the type of the value.  Int, string, etc
  989.       $value = $this->_toType($value);
  990.       if ($key === '0') $key = '__!YAMLZero';
  991.       $array[$key] = $value;
  992.     } else {
  993.       $array = array ($line);
  994.     }
  995.     return $array;
  996.  
  997.   }
  998.  
  999.  
  1000.   private function returnArrayElement ($line) {
  1001.      if (strlen($line) <= 1) return array(array()); // Weird %)
  1002.      $array = array();
  1003.      $value   = trim(substr($line,1));
  1004.      $value   = $this->_toType($value);
  1005.      $array[] = $value;
  1006.      return $array;
  1007.   }
  1008.  
  1009.  
  1010.   private function nodeContainsGroup ($line) {    
  1011.     $symbolsForReference = 'A-z0-9_\-';
  1012.     if (strpos($line, '&') === false && strpos($line, '*') === false) return false; // Please die fast ;-)
  1013.     if ($line[0] == '&' && preg_match('/^(&['.$symbolsForReference.']+)/', $line, $matches)) return $matches[1];
  1014.     if ($line[0] == '*' && preg_match('/^(\*['.$symbolsForReference.']+)/', $line, $matches)) return $matches[1];
  1015.     if (preg_match('/(&['.$symbolsForReference.']+)$/', $line, $matches)) return $matches[1];
  1016.     if (preg_match('/(\*['.$symbolsForReference.']+$)/', $line, $matches)) return $matches[1];
  1017.     if (preg_match ('#^\s*<<\s*:\s*(\*[^\s]+).*$#', $line, $matches)) return $matches[1];
  1018.     return false;
  1019.  
  1020.   }
  1021.  
  1022.   private function addGroup ($line, $group) {
  1023.     if ($group[0] == '&') $this->_containsGroupAnchor = substr ($group, 1);
  1024.     if ($group[0] == '*') $this->_containsGroupAlias = substr ($group, 1);
  1025.     //print_r ($this->path);
  1026.   }
  1027.  
  1028.   private function stripGroup ($line, $group) {
  1029.     $line = trim(str_replace($group, '', $line));
  1030.     return $line;
  1031.   }
  1032. }
  1033.  
  1034. // Enable use of Spyc from command line
  1035. // The syntax is the following: php spyc.php spyc.yaml
  1036.  
  1037. define ('SPYC_FROM_COMMAND_LINE', false);
  1038.  
  1039. do {
  1040.   if (!SPYC_FROM_COMMAND_LINE) break;
  1041.   if (empty ($_SERVER['argc']) || $_SERVER['argc'] < 2) break;
  1042.   if (empty ($_SERVER['PHP_SELF']) || $_SERVER['PHP_SELF'] != 'spyc.php') break;
  1043.   $file = $argv[1];
  1044.   printf ("Spyc loading file: %s\n", $file);
  1045.   print_r (spyc_load_file ($file));
  1046. } while (0);
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement