PageRenderTime 43ms CodeModel.GetById 17ms RepoModel.GetById 0ms app.codeStats 0ms

/vendor/nette/neon/src/Neon/Decoder.php

https://gitlab.com/kubinos/writeoff
PHP | 332 lines | 267 code | 43 blank | 22 comment | 81 complexity | 3d6204087fe526b03fc01e77937a9fe1 MD5 | raw file
  1. <?php
  2. /**
  3. * This file is part of the Nette Framework (http://nette.org)
  4. * Copyright (c) 2004 David Grudl (http://davidgrudl.com)
  5. */
  6. namespace Nette\Neon;
  7. /**
  8. * Parser for Nette Object Notation.
  9. * @internal
  10. */
  11. class Decoder
  12. {
  13. /** @var array */
  14. public static $patterns = array(
  15. '
  16. \'[^\'\n]*\' |
  17. "(?: \\\\. | [^"\\\\\n] )*"
  18. ', // string
  19. '
  20. (?: [^#"\',:=[\]{}()\x00-\x20!`-] | [:-][^"\',\]})\s] )
  21. (?:
  22. [^,:=\]})(\x00-\x20]+ |
  23. :(?! [\s,\]})] | $ ) |
  24. [\ \t]+ [^#,:=\]})(\x00-\x20]
  25. )*
  26. ', // literal / boolean / integer / float
  27. '
  28. [,:=[\]{}()-]
  29. ', // symbol
  30. '?:\#.*', // comment
  31. '\n[\t\ ]*', // new line + indent
  32. '?:[\t\ ]+', // whitespace
  33. );
  34. private static $brackets = array(
  35. '[' => ']',
  36. '{' => '}',
  37. '(' => ')',
  38. );
  39. /** @var string */
  40. private $input;
  41. /** @var array */
  42. private $tokens;
  43. /** @var int */
  44. private $pos;
  45. /**
  46. * Decodes a NEON string.
  47. * @param string
  48. * @return mixed
  49. */
  50. public function decode($input)
  51. {
  52. if (!is_string($input)) {
  53. throw new \InvalidArgumentException(sprintf('Argument must be a string, %s given.', gettype($input)));
  54. } elseif (substr($input, 0, 3) === "\xEF\xBB\xBF") { // BOM
  55. $input = substr($input, 3);
  56. }
  57. $this->input = "\n" . str_replace("\r", '', $input); // \n forces indent detection
  58. $pattern = '~(' . implode(')|(', self::$patterns) . ')~Amix';
  59. $this->tokens = preg_split($pattern, $this->input, -1, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_OFFSET_CAPTURE | PREG_SPLIT_DELIM_CAPTURE);
  60. $last = end($this->tokens);
  61. if ($this->tokens && !preg_match($pattern, $last[0])) {
  62. $this->pos = count($this->tokens) - 1;
  63. $this->error();
  64. }
  65. $this->pos = 0;
  66. $res = $this->parse(NULL);
  67. while (isset($this->tokens[$this->pos])) {
  68. if ($this->tokens[$this->pos][0][0] === "\n") {
  69. $this->pos++;
  70. } else {
  71. $this->error();
  72. }
  73. }
  74. return $res;
  75. }
  76. /**
  77. * @param string indentation (for block-parser)
  78. * @param mixed
  79. * @return array
  80. */
  81. private function parse($indent, $result = NULL, $key = NULL, $hasKey = FALSE)
  82. {
  83. $inlineParser = $indent === FALSE;
  84. $value = NULL;
  85. $hasValue = FALSE;
  86. $tokens = $this->tokens;
  87. $n = & $this->pos;
  88. $count = count($tokens);
  89. $mainResult = & $result;
  90. for (; $n < $count; $n++) {
  91. $t = $tokens[$n][0];
  92. if ($t === ',') { // ArrayEntry separator
  93. if ((!$hasKey && !$hasValue) || !$inlineParser) {
  94. $this->error();
  95. }
  96. $this->addValue($result, $hasKey ? $key : NULL, $hasValue ? $value : NULL);
  97. $hasKey = $hasValue = FALSE;
  98. } elseif ($t === ':' || $t === '=') { // KeyValuePair separator
  99. if ($hasValue && (is_array($value) || is_object($value))) {
  100. $this->error('Unacceptable key');
  101. } elseif ($hasKey && $key === NULL && $hasValue && !$inlineParser) {
  102. $n++;
  103. $result[] = $this->parse($indent . ' ', array(), $value, TRUE);
  104. $newIndent = isset($tokens[$n], $tokens[$n + 1]) ? (string) substr($tokens[$n][0], 1) : ''; // not last
  105. if (strlen($newIndent) > strlen($indent)) {
  106. $n++;
  107. $this->error('Bad indentation');
  108. } elseif (strlen($newIndent) < strlen($indent)) {
  109. return $mainResult; // block parser exit point
  110. }
  111. $hasKey = $hasValue = FALSE;
  112. } elseif ($hasKey || !$hasValue) {
  113. $this->error();
  114. } else {
  115. $key = (string) $value;
  116. $hasKey = TRUE;
  117. $hasValue = FALSE;
  118. $result = & $mainResult;
  119. }
  120. } elseif ($t === '-') { // BlockArray bullet
  121. if ($hasKey || $hasValue || $inlineParser) {
  122. $this->error();
  123. }
  124. $key = NULL;
  125. $hasKey = TRUE;
  126. } elseif (isset(self::$brackets[$t])) { // Opening bracket [ ( {
  127. if ($hasValue) {
  128. if ($t !== '(') {
  129. $this->error();
  130. }
  131. $n++;
  132. if ($value instanceof Entity && $value->value === Neon::CHAIN) {
  133. end($value->attributes)->attributes = $this->parse(FALSE, array());
  134. } else {
  135. $value = new Entity($value, $this->parse(FALSE, array()));
  136. }
  137. } else {
  138. $n++;
  139. $value = $this->parse(FALSE, array());
  140. }
  141. $hasValue = TRUE;
  142. if (!isset($tokens[$n]) || $tokens[$n][0] !== self::$brackets[$t]) { // unexpected type of bracket or block-parser
  143. $this->error();
  144. }
  145. } elseif ($t === ']' || $t === '}' || $t === ')') { // Closing bracket ] ) }
  146. if (!$inlineParser) {
  147. $this->error();
  148. }
  149. break;
  150. } elseif ($t[0] === "\n") { // Indent
  151. if ($inlineParser) {
  152. if ($hasKey || $hasValue) {
  153. $this->addValue($result, $hasKey ? $key : NULL, $hasValue ? $value : NULL);
  154. $hasKey = $hasValue = FALSE;
  155. }
  156. } else {
  157. while (isset($tokens[$n + 1]) && $tokens[$n + 1][0][0] === "\n") {
  158. $n++; // skip to last indent
  159. }
  160. if (!isset($tokens[$n + 1])) {
  161. break;
  162. }
  163. $newIndent = (string) substr($tokens[$n][0], 1);
  164. if ($indent === NULL) { // first iteration
  165. $indent = $newIndent;
  166. }
  167. $minlen = min(strlen($newIndent), strlen($indent));
  168. if ($minlen && (string) substr($newIndent, 0, $minlen) !== (string) substr($indent, 0, $minlen)) {
  169. $n++;
  170. $this->error('Invalid combination of tabs and spaces');
  171. }
  172. if (strlen($newIndent) > strlen($indent)) { // open new block-array or hash
  173. if ($hasValue || !$hasKey) {
  174. $n++;
  175. $this->error('Bad indentation');
  176. }
  177. $this->addValue($result, $key, $this->parse($newIndent));
  178. $newIndent = isset($tokens[$n], $tokens[$n + 1]) ? (string) substr($tokens[$n][0], 1) : ''; // not last
  179. if (strlen($newIndent) > strlen($indent)) {
  180. $n++;
  181. $this->error('Bad indentation');
  182. }
  183. $hasKey = FALSE;
  184. } else {
  185. if ($hasValue && !$hasKey) { // block items must have "key"; NULL key means list item
  186. break;
  187. } elseif ($hasKey) {
  188. $this->addValue($result, $key, $hasValue ? $value : NULL);
  189. if ($key !== NULL && !$hasValue && $newIndent === $indent && isset($tokens[$n + 1]) && $tokens[$n + 1][0] === '-') {
  190. $result = & $result[$key];
  191. }
  192. $hasKey = $hasValue = FALSE;
  193. }
  194. }
  195. if (strlen($newIndent) < strlen($indent)) { // close block
  196. return $mainResult; // block parser exit point
  197. }
  198. }
  199. } elseif ($hasValue) { // Value
  200. if ($value instanceof Entity) { // Entity chaining
  201. if ($value->value !== Neon::CHAIN) {
  202. $value = new Entity(Neon::CHAIN, array($value));
  203. }
  204. $value->attributes[] = new Entity($t);
  205. } else {
  206. $this->error();
  207. }
  208. } else { // Value
  209. static $consts = array(
  210. 'true' => TRUE, 'True' => TRUE, 'TRUE' => TRUE, 'yes' => TRUE, 'Yes' => TRUE, 'YES' => TRUE, 'on' => TRUE, 'On' => TRUE, 'ON' => TRUE,
  211. 'false' => FALSE, 'False' => FALSE, 'FALSE' => FALSE, 'no' => FALSE, 'No' => FALSE, 'NO' => FALSE, 'off' => FALSE, 'Off' => FALSE, 'OFF' => FALSE,
  212. 'null' => 0, 'Null' => 0, 'NULL' => 0,
  213. );
  214. if ($t[0] === '"') {
  215. $value = preg_replace_callback('#\\\\(?:ud[89ab][0-9a-f]{2}\\\\ud[c-f][0-9a-f]{2}|u[0-9a-f]{4}|x[0-9a-f]{2}|.)#i', array($this, 'cbString'), substr($t, 1, -1));
  216. } elseif ($t[0] === "'") {
  217. $value = substr($t, 1, -1);
  218. } elseif (isset($consts[$t]) && (!isset($tokens[$n + 1][0]) || ($tokens[$n + 1][0] !== ':' && $tokens[$n + 1][0] !== '='))) {
  219. $value = $consts[$t] === 0 ? NULL : $consts[$t];
  220. } elseif (is_numeric($t)) {
  221. $value = $t * 1;
  222. } elseif (preg_match('#0x[0-9a-fA-F]+\z#A', $t)) {
  223. $value = hexdec($t);
  224. } elseif (preg_match('#\d\d\d\d-\d\d?-\d\d?(?:(?:[Tt]| +)\d\d?:\d\d:\d\d(?:\.\d*)? *(?:Z|[-+]\d\d?(?::\d\d)?)?)?\z#A', $t)) {
  225. $value = new \DateTime($t);
  226. } else { // literal
  227. $value = $t;
  228. }
  229. $hasValue = TRUE;
  230. }
  231. }
  232. if ($inlineParser) {
  233. if ($hasKey || $hasValue) {
  234. $this->addValue($result, $hasKey ? $key : NULL, $hasValue ? $value : NULL);
  235. }
  236. } else {
  237. if ($hasValue && !$hasKey) { // block items must have "key"
  238. if ($result === NULL) {
  239. $result = $value; // simple value parser
  240. } else {
  241. $this->error();
  242. }
  243. } elseif ($hasKey) {
  244. $this->addValue($result, $key, $hasValue ? $value : NULL);
  245. }
  246. }
  247. return $mainResult;
  248. }
  249. private function addValue(& $result, $key, $value)
  250. {
  251. if ($key === NULL) {
  252. $result[] = $value;
  253. } elseif ($result && array_key_exists($key, $result)) {
  254. $this->error("Duplicated key '$key'");
  255. } else {
  256. $result[$key] = $value;
  257. }
  258. }
  259. private function cbString($m)
  260. {
  261. static $mapping = array('t' => "\t", 'n' => "\n", 'r' => "\r", 'f' => "\x0C", 'b' => "\x08", '"' => '"', '\\' => '\\', '/' => '/', '_' => "\xc2\xa0");
  262. $sq = $m[0];
  263. if (isset($mapping[$sq[1]])) {
  264. return $mapping[$sq[1]];
  265. } elseif ($sq[1] === 'u' && strlen($sq) >= 6) {
  266. $lead = hexdec(substr($sq, 2, 4));
  267. $tail = hexdec(substr($sq, 8, 4));
  268. $code = $tail ? (0x2400 + (($lead - 0xD800) << 10) + $tail) : $lead;
  269. if ($code >= 0xD800 && $code <= 0xDFFF) {
  270. $this->error("Invalid UTF-8 (lone surrogate) $sq");
  271. }
  272. return iconv('UTF-32BE', 'UTF-8//IGNORE', pack('N', $code));
  273. } elseif ($sq[1] === 'x' && strlen($sq) === 4) {
  274. return chr(hexdec(substr($sq, 2)));
  275. } else {
  276. $this->error("Invalid escaping sequence $sq");
  277. }
  278. }
  279. private function error($message = "Unexpected '%s'")
  280. {
  281. $last = isset($this->tokens[$this->pos]) ? $this->tokens[$this->pos] : NULL;
  282. $offset = $last ? $last[1] : strlen($this->input);
  283. $text = substr($this->input, 0, $offset);
  284. $line = substr_count($text, "\n");
  285. $col = $offset - strrpos("\n" . $text, "\n") + 1;
  286. $token = $last ? str_replace("\n", '<new line>', substr($last[0], 0, 40)) : 'end';
  287. throw new Exception(str_replace('%s', $token, $message) . " on line $line, column $col.");
  288. }
  289. }