File modules/util/Spyc.class.php

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