1<?php
2/**
3 * from https://github.com/atk14/MiniYaml and namespaced for this plugin
4 */
5namespace dokuwiki\plugin\questionnaire;
6
7
8/**
9* YAML dumper & loader
10*
11* It can dump and load associative or indexed arrays.
12*
13* Usage:
14*    $ary = miniYAML::Load($yaml_str);
15*    $yaml = miniYAML::Dump($ary);
16*
17* TODO:
18*   Parser nenacte takovy zapis, ve kterem jsou vsechny radky odsazeny o spolecny indent.
19*
20* Changelog:
21*
22* 2013-03-02
23*     Better string escaping
24*     An associative array can be passed to miniYAML::Dump()
25*
26* 2008-04-09
27*     Vylepseno rozpoznavani asociativniho pole.
28*
29* 2008-01-28
30*      Prepracovani nacitani YAML dokumentu. Parser si nyni poradi i s indexovym pole,
31*      jeho prevky jsou jine pole (indexove nebo asociativni):
32*            ---
33*            - element 1
34*            - - element 2.1
35*              - element 2.2
36*            - key1: val1
37*              key2: val2
38*
39* 2008-01-25
40*     Doplnena schopnost zpracovat prazdne indexove pole.
41*           ---
42*           status: success
43*           message: Ok
44*           data:
45*             domain: test.cz
46*             registrant: ZUZANA-PROKOPOVA
47*             nsset: HOSTING-NS
48*             admin:
49*             - JAN-PROKOP
50*             - JANA-PROKOPOVA
51*             tempcontact: []
52*
53*             registrar: REG-GENREG
54*             create_date: 2001-01-10
55*             expiry_date: 2014-01-11
56*
57* 2008-01-09
58*    Dodelana schopnost zpracovavat indexovana pole. Priklad YAML:
59*           ---
60*           command: check domains availability
61*           params:
62*             domains:
63*             - test1.cz
64*             - test2.cz
65*             - test3.cz
66*             - test4.cz
67*/
68class miniYAML{
69
70  protected $_Lines = array();
71  protected $nullable = true;
72
73  function __construct($options = array()){
74    $options += array(
75      "nullable" => true, // Whether to consider strings null and NULL as true NULL?
76    );
77
78    $this->nullable = $options["nullable"];
79  }
80
81  /**
82  * Prevede YAML zapis na pole.
83  *
84  * @static
85  * @access public
86  * @param string $yaml      zapis struktury v YAML
87  * @return array
88  */
89  static function Load($yaml,$options = array()){
90		$options = array_merge(array(
91			"interpret_php" => false,
92			"values" => array(),
93		),$options);
94
95		if($options["interpret_php"]){
96			$yaml = miniYAML::InterpretPHP($yaml,$options["values"]);
97		}
98
99    unset($options["interpret_php"]);
100    unset($options["values"]);
101
102    $obj = new miniYAML($options);
103    return $obj->_load($yaml);
104  }
105
106  /**
107  * Prevedete pole na do YAML zapisu.
108  *
109  * Reverzni metoda k miniYAML::Load();
110  *
111  * @static
112  * @access public
113  * @param array $ar
114  * @return string
115  */
116  static function Dump($ar,$options = array()){
117    $obj = new miniYAML($options);
118    $out = "---";
119		if($obj->_isIndexedArray($ar)){
120			$out .= "\n".$obj->_dumpIndexedArray($ar,0);
121		}elseif(is_array($ar)){
122			$out .= "\n".$obj->_dumpHashArray($ar,0);
123		}
124		$out .= "\n";
125		return $out;
126  }
127
128	/**
129	* $yaml = miniYAML::InterpretPHP('
130	*		---
131	*		login: <?= $login ?>
132	*
133	*		password: <?= $password ?>
134	*	',array(
135	*		"login" => "admin",
136	*		"password" => "magic"
137	*	));
138	*
139	* Note: Think of newline removal at the end of php end tag!
140	*/
141	static function InterpretPHP($__yaml,$__values = array()){
142		foreach($__values as $__k => $__v){
143			eval("\$$__k = \$__v;");
144		}
145		$__error_reporting = error_reporting(0);
146		ob_start();
147		eval("?".">".$__yaml);
148		$__yaml = ob_get_contents();
149		ob_end_clean();
150		error_reporting($__error_reporting);
151		return $__yaml;
152	}
153
154  function _load($yaml){
155    $yaml = str_replace("\r","",$yaml);
156    $ar = explode("\n",$yaml);
157    $this->_Lines = array();
158    $got_structure_begin = false;
159    for($i=0;$i<sizeof($ar);$i++){
160      if(trim($ar[$i]) == "---"){ // zacatek nove struktury - muze byt v zapisu pouze 1x a to na jejim zacatku
161        if($got_structure_begin){ return null; }
162        $got_structure_begin = true;
163        continue;
164      }
165      if($this->_containsData($ar[$i])){
166        $got_structure_begin = true;
167        $this->_Lines[] = $ar[$i];
168      }
169    }
170
171    $out = $this->_readVar($this->_Lines,$lines_read);
172    // nasledujici podminka zachyti situaci, kdyz nejsou zpracovany vsechny radky a uz mame nejaky hotovy vystup...
173    // muze se to tykat napr. tohoto neplatneho zapisu:
174    //      ---
175    //      - jedna
176    //      - dve
177    //      klic:
178    if($lines_read!=sizeof($this->_Lines)){
179      return null;
180    }
181    return $out;
182  }
183
184  /**
185  * Spocita mezery na zacatku radku.
186  *
187  * @access private
188  * @param string $line
189  * @return int
190  */
191  function _getIndent($line){
192    preg_match("/^( *)/",(string)$line,$matches);
193    return strlen($matches[1]);
194  }
195
196  /**
197  * Smaze z radky odsazeni.
198  *
199  * @access private
200  * @param string $line
201  * @param int $indent          bude-li -1, bude delka odsazeni urcena automaticky
202  * @return string
203  */
204  function _stripIndent($line, $indent = -1){
205    if ($indent == -1){
206      $indent = $this->_getIndent($line);
207    }
208    return substr ($line, $indent);
209  }
210
211  /**
212  * Vysekne z pole radku blok s danym odsazenim.
213  *
214  * Vybere vsechny radky, ktere maji alespon urcene odsazeni,
215  * ale i vsechny radky s odsazenim delsim.
216  *
217  * Zamerne vsak neni porovnavana delka odsazeni prvniho radku.
218  * Navic je prvnimu radku toto odsazeni automaticky nastaveno.
219  *
220  * @access public
221  * @param int $start_at    prvni index v poli
222  * @param int $indent       delka nejmensiho odsazeni
223  * @param string[]  $lines   pole radek; pokud nebude nastaveno, bude se uvazovat $this->_Lines
224  * @return string[]
225  */
226  function _cutOutBlock($start_at,$indent,$lines = null){
227    if(!isset($lines)){ $lines = &$this->_Lines; }
228    $out = array();
229    $out[] = str_repeat(" ",$indent).substr($lines[$start_at],$indent);
230    for($i=$start_at+1;$i<sizeof($lines);$i++){
231      $_indent = $this->_getIndent($lines[$i]);
232      if($_indent<$indent){
233        break;
234      }
235      $out[] = $lines[$i];
236    }
237    return $out;
238  }
239
240  /**
241  * Provede vyseknuti bloku. Navic vsem radkum odstrani indent.
242  *
243  * @access public
244  * @param int $start_at    prvni index v poli
245  * @param int $indent       delka nejmensiho odsazeni
246  * @param string[]  $lines   pole radek; pokud nebude nastaveno, bude se uvazovat $this->_Lines
247  * @return string[]
248  */
249  function _cutOutBlock_Stripped($start_at,$indent,$lines = null){
250    $lines = $this->_cutOutBlock($start_at,$indent,$lines);
251    for($i=0;$i<sizeof($lines);$i++){
252      $lines[$i] = substr($lines[$i],$indent);
253    }
254    return $lines;
255  }
256
257  /**
258  * Nacte datovou strukturu z daneho pole radku.
259  *
260  * @access private
261  * @param string[] $block
262  * @param int &$lines_read    pocet radku pole potrebnych pro nacteni struktury
263  * @param array $options      parametry nacitani
264  * @return mixed              indexove pole, hash pole nebo string
265  */
266  function _readVar($block,&$lines_read,$options = array()){
267    $options = array_merge(array(
268      "testing_for_array" => true
269    ),$options);
270
271    if(is_string($block)){ $block = array($block); }
272
273    $lines_read = 0;
274
275		if(sizeof($block)==0){ return null; }
276
277    if($options["testing_for_array"]){
278      if(preg_match("/^- /",$block[0])){
279        return $this->_readIndexedArray($block,$lines_read);
280      }
281      if(preg_match("/^[^\\s\"]+?:(\\s+[^\\s].*|\\s*)$/",$block[0])){
282        return $this->_readHashArray($block,$lines_read);
283			}
284    }
285
286    if(sizeof($block)==1){ // toto je spatne!!!! zde zapadne i indexove pole o velikosti 1
287      $lines_read = 1;
288      $out = trim($block[0]);
289      if($out == "[]"){ return array(); }
290      $this->_unescapeString($out);
291      return $out;
292    }
293  }
294
295  /**
296  * Nacte indexove pole z pole radku
297  *
298  * @access private
299  * @param string[] $block
300  * @param int &$lines_read    pocet radku pole potrebnych pro nacteni vraceneho pole
301  * @return array
302  */
303  function _readIndexedArray($block,&$lines_read){
304    $out = array();
305    $lines_read = 0;
306    for($i=0;$i<sizeof($block);$i++){
307      $line = $block[$i];
308      if(!preg_match("/^- /",$line)){
309        break;
310      }
311      $value_block = $this->_cutOutBlock_Stripped($i,2,$block);
312      $out[] = $this->_readVar($value_block,$li);
313      $i += $li-1; // -1 -> zaciname cist na akt. radku
314    }
315    $lines_read = $i;
316    return $out;
317  }
318
319  /**
320  * Nacte asociativni pole (hash) z pole radku.
321  *
322  * @access private
323  * @param string[] $block
324  * @param int &$lines_read    pocet radku pole potrebnych pro nacteni vraceneho pole
325  * @return array
326  */
327  function _readHashArray($block,&$lines_read){
328    $out = array();
329    $lines_read = 0;
330    for($i=0;$i<sizeof($block);$i++){
331      $line = $block[$i];
332      $next_line = null;
333      if(isset($block[$i+1])){ $next_line = $block[$i+1]; }
334      if(!preg_match("/^(.+?):(.*)/",$line,$matches)){
335        $i--;
336        break;
337      }
338      $key = $matches[1];
339      $_values = trim($matches[2]);
340      $next_line_indent = $this->_getIndent($next_line);
341      if($next_line_indent>0 || preg_match("/^- /",(string)$next_line)){
342        $value_block = $this->_cutOutBlock_Stripped($i+1,$next_line_indent,$block);
343        $value = $this->_readVar($value_block,$li);
344        $i += $li;
345      }else{
346        $value = $this->_readVar($_values,$li,array("testing_for_array" => false));
347      }
348			if(preg_match('/^\s/',$key)){
349				throw new Exception("token cannot begin with tabulator or other white character on line: $line");
350			}
351      $out[$key] = $value;
352    }
353    $lines_read = $i;
354    return $out;
355  }
356
357  function _isComment($line){
358    if(preg_match("/^#/",$line)){ return true;}
359    return false;
360  }
361
362  function _isEmpty($line){
363    return (strlen(trim($line))==0);
364  }
365
366  function _containsData($line){
367    return !($this->_isComment($line) || $this->_isEmpty($line));
368  }
369
370  function _unescapeString(&$str){
371    if(preg_match('/^("(.*)"|\'(.*)\')/',$str,$matches)){
372      $str = end($matches);
373      $str = preg_replace('/(\'\'|\\\\\')/',"'",$str);
374      $str = preg_replace('/\\\\"/','"',$str);
375      return true;
376    }
377    if($this->nullable && ($str==="null" || $str==="NULL")){
378       $str = null;
379       return true;
380    }
381    return false;
382  }
383
384  /*
385   * Metody pro dumpovani.
386   */
387
388  function _dumpVar($var,$indent = 0){
389    $out = array();
390    if($this->_isIndexedArray($var)){
391      $out[] = sizeof($var)==0 ? "[]" : "";
392      $out[] = $this->_dumpIndexedArray($var,$indent); // indexove pole se tiskne se stejnym indentem
393    }elseif(is_array($var)){
394      $out[] = "";
395      $out[] = $this->_dumpHashArray($var,$indent + 1);
396    }else{
397      $out[] = $this->_dumpString($var); // schvalne je vynechan $indent, ident je pred klicem
398    }
399    return join("\n",$out);
400  }
401
402  function _dumpString($str,$indent = 0){
403    $patterns_to_escape = array(
404      "/^\\s+/", "/\\s+$/","/\\n/",
405      "/^yes$/i", "/^on$/i", "/^\\+$/", "/^y$/", "/^true$/i",
406      "/^no$/i", "/^off$/i", "/^-$/", "/^n$/", "/^false$/i",
407      "/^null$/i", "/^~$/", "/^$/",
408      "/^\\-?.inf$/i", "/^.nan$/i",
409      "/^\"/", "/^'/", "/#/",
410      "/^{/", "/^}/", "/^\\[/", "/^\\]/", "/^:/", "/^=$/", "/^\\?$/", "/^\\|/", "/^>/", "/^-$/", "/^<<$/",
411      "/^!/", "/^\\*/", "/^\\&/",
412			"/:\s/",
413			"/^:/",
414			"/:$/",
415    );
416
417    if(is_null($str) && $this->nullable){
418      $str = "NULL";
419    }elseif(is_numeric($str) || is_numeric(str_replace("_","",$str))){
420      $str = $this->_escapeString($str);
421    }else{
422      $_escaped = false;
423      foreach($patterns_to_escape as $pattern){
424        if(preg_match($pattern,$str)){
425          $str = $this->_escapeString($str);
426          $_escaped = true;
427          break;
428        }
429      }
430    }
431    return $this->_dumpIndent($indent).$str;
432  }
433
434  function _isIndexedArray($ar){
435    if(!is_array($ar)){ return false; }
436    $expected_key = 0;
437    foreach(array_keys($ar) as $_key){
438      if(!is_int($_key) || $_key!=$expected_key){ return false; }
439      $expected_key++;
440    }
441    return true;
442  }
443
444  function _dumpIndexedArray($ar,$indent){
445    $out = array();
446    foreach($ar as $_value){
447      if($this->_isIndexedArray($_value) && sizeof($_value)>0){
448        $_dump = $this->_dumpIndexedArray($_value,$indent + 1); // "- "
449        $_prefix = $this->_dumpIndent($indent)."- ";
450        $out[] = $_prefix.substr($_dump,strlen($_prefix));
451      }elseif(is_array($_value) && sizeof($_value)>0){
452        $_dump = $this->_dumpHashArray($_value,$indent + 1); // "- "
453        $_prefix = $this->_dumpIndent($indent)."- ";
454        $out[] = $_prefix.substr($_dump,strlen($_prefix));
455        //$out[] = $_prefix.$_dump;
456      }else{
457        $out[] = $this->_dumpIndent($indent)."- ".$this->_dumpVar($_value,$indent + 2); // "- "
458      }
459    }
460    return join("\n",$out);
461  }
462
463  function _dumpHashArray($ar,$indent){
464    $out = array();
465    foreach($ar as $_key => $_value){
466      $out[] = $this->_dumpIndent($indent).$this->_dumpString($_key).": ".$this->_dumpVar($_value,$indent);
467    }
468    return join("\n",$out);
469  }
470
471  function _dumpIndent($indent){
472    if($indent<=0){ return ""; }
473    return str_repeat(" ",$indent * 2);
474  }
475
476  function _escapeString($str){
477    return "\"".str_replace("\"","\\\"",$str)."\"";
478  }
479}
480// vim: set expandtab:
481