Packages

  • Nette
    • Application
      • Diagnostics
      • Responses
      • Routers
      • UI
    • Caching
      • Storages
    • ComponentModel
    • Config
      • Adapters
      • Extensions
    • Database
      • Diagnostics
      • Drivers
      • Reflection
      • Table
    • DI
      • Diagnostics
    • Diagnostics
    • Forms
      • Controls
      • Rendering
    • Http
    • Iterators
    • Latte
      • Macros
    • Loaders
    • Localization
    • Mail
    • Reflection
    • Security
      • Diagnostics
    • Templating
    • Utils
      • PhpGenerator
  • NetteModule
  • none

Classes

Exceptions

  • Overview
  • Package
  • Class
  • Tree
  • Deprecated
  • Other releases
  • Nette homepage
  1: <?php
  2: 
  3: /**
  4:  * This file is part of the Nette Framework (https://nette.org)
  5:  * Copyright (c) 2004 David Grudl (http://davidgrudl.com)
  6:  * @package Nette\Utils
  7:  */
  8: 
  9: 
 10: 
 11: /**
 12:  * Simple parser & generator for Nette Object Notation.
 13:  *
 14:  * @author     David Grudl
 15:  * @package Nette\Utils
 16:  */
 17: class Neon extends Object
 18: {
 19:     const BLOCK = 1;
 20: 
 21:     /** @var array */
 22:     private static $patterns = array(
 23:         '
 24:             \'[^\'\n]*\' |
 25:             "(?: \\\\. | [^"\\\\\n] )*"
 26:         ', // string
 27:         '
 28:             (?: [^#"\',:=[\]{}()\x00-\x20!`-] | [:-][^"\',\]})\s] )
 29:             (?:
 30:                 [^,:=\]})(\x00-\x20]+ |
 31:                 :(?! [\s,\]})] | $ ) |
 32:                 [\ \t]+ [^#,:=\]})(\x00-\x20]
 33:             )*
 34:         ', // literal / boolean / integer / float
 35:         '
 36:             [,:=[\]{}()-]
 37:         ', // symbol
 38:         '?:\#.*', // comment
 39:         '\n[\t\ ]*', // new line + indent
 40:         '?:[\t\ ]+', // whitespace
 41:     );
 42: 
 43:     /** @var Tokenizer */
 44:     private static $tokenizer;
 45: 
 46:     private static $brackets = array(
 47:         '[' => ']',
 48:         '{' => '}',
 49:         '(' => ')',
 50:     );
 51: 
 52:     /** @var int */
 53:     private $n = 0;
 54: 
 55:     /** @var bool */
 56:     private $indentTabs;
 57: 
 58: 
 59:     /**
 60:      * Returns the NEON representation of a value.
 61:      * @param  mixed
 62:      * @param  int
 63:      * @return string
 64:      */
 65:     public static function encode($var, $options = NULL)
 66:     {
 67:         if ($var instanceof DateTime) {
 68:             return $var->format('Y-m-d H:i:s O');
 69: 
 70:         } elseif ($var instanceof NeonEntity) {
 71:             return self::encode($var->value) . '(' . substr(self::encode($var->attributes), 1, -1) . ')';
 72:         }
 73: 
 74:         if (is_object($var)) {
 75:             $obj = $var; $var = array();
 76:             foreach ($obj as $k => $v) {
 77:                 $var[$k] = $v;
 78:             }
 79:         }
 80: 
 81:         if (is_array($var)) {
 82:             $isList = Validators::isList($var);
 83:             $s = '';
 84:             if ($options & self::BLOCK) {
 85:                 if (count($var) === 0) {
 86:                     return "[]";
 87:                 }
 88:                 foreach ($var as $k => $v) {
 89:                     $v = self::encode($v, self::BLOCK);
 90:                     $s .= ($isList ? '-' : self::encode($k) . ':')
 91:                         . (Strings::contains($v, "\n") ? "\n\t" . str_replace("\n", "\n\t", $v) : ' ' . $v)
 92:                         . "\n";
 93:                     continue;
 94:                 }
 95:                 return $s;
 96: 
 97:             } else {
 98:                 foreach ($var as $k => $v) {
 99:                     $s .= ($isList ? '' : self::encode($k) . ': ') . self::encode($v) . ', ';
100:                 }
101:                 return ($isList ? '[' : '{') . substr($s, 0, -2) . ($isList ? ']' : '}');
102:             }
103: 
104:         } elseif (is_string($var) && !is_numeric($var)
105:             && !preg_match('~[\x00-\x1F]|^\d{4}|^(true|false|yes|no|on|off|null)\z~i', $var)
106:             && preg_match('~^' . self::$patterns[1] . '\z~x', $var) // 1 = literals
107:         ) {
108:             return $var;
109: 
110:         } elseif (is_float($var)) {
111:             $var = json_encode($var);
112:             return Strings::contains($var, '.') ? $var : $var . '.0';
113: 
114:         } else {
115:             return json_encode($var);
116:         }
117:     }
118: 
119: 
120:     /**
121:      * Decodes a NEON string.
122:      * @param  string
123:      * @return mixed
124:      */
125:     public static function decode($input)
126:     {
127:         if (!is_string($input)) {
128:             throw new InvalidArgumentException("Argument must be a string, " . gettype($input) . " given.");
129:         }
130:         if (!self::$tokenizer) {
131:             self::$tokenizer = new Tokenizer(self::$patterns, 'mix');
132:         }
133: 
134:         if (substr($input, 0, 3) === "\xEF\xBB\xBF") { // BOM
135:             $input = substr($input, 3);
136:         }
137:         $input = str_replace("\r", '', $input);
138:         self::$tokenizer->tokenize($input);
139: 
140:         $parser = new self;
141:         $res = $parser->parse(0);
142: 
143:         while (isset(self::$tokenizer->tokens[$parser->n])) {
144:             if (self::$tokenizer->tokens[$parser->n][0] === "\n") {
145:                 $parser->n++;
146:             } else {
147:                 $parser->error();
148:             }
149:         }
150:         return $res;
151:     }
152: 
153: 
154:     /**
155:      * @param  int  indentation (for block-parser)
156:      * @param  mixed
157:      * @return array
158:      */
159:     private function parse($indent = NULL, $result = NULL)
160:     {
161:         $inlineParser = $indent === NULL;
162:         $value = $key = NULL;
163:         $hasValue = $hasKey = FALSE;
164:         $tokens = self::$tokenizer->tokens;
165:         $n = & $this->n;
166:         $count = count($tokens);
167: 
168:         for (; $n < $count; $n++) {
169:             $t = $tokens[$n];
170: 
171:             if ($t === ',') { // ArrayEntry separator
172:                 if ((!$hasKey && !$hasValue) || !$inlineParser) {
173:                     $this->error();
174:                 }
175:                 $this->addValue($result, $hasKey, $key, $hasValue ? $value : NULL);
176:                 $hasKey = $hasValue = FALSE;
177: 
178:             } elseif ($t === ':' || $t === '=') { // KeyValuePair separator
179:                 if ($hasKey || !$hasValue) {
180:                     $this->error();
181:                 }
182:                 if (is_array($value) || is_object($value)) {
183:                     $this->error('Unacceptable key');
184:                 }
185:                 $key = (string) $value;
186:                 $hasKey = TRUE;
187:                 $hasValue = FALSE;
188: 
189:             } elseif ($t === '-') { // BlockArray bullet
190:                 if ($hasKey || $hasValue || $inlineParser) {
191:                     $this->error();
192:                 }
193:                 $key = NULL;
194:                 $hasKey = TRUE;
195: 
196:             } elseif (isset(self::$brackets[$t])) { // Opening bracket [ ( {
197:                 if ($hasValue) {
198:                     if ($t !== '(') {
199:                         $this->error();
200:                     }
201:                     $n++;
202:                     $entity = new NeonEntity;
203:                     $entity->value = $value;
204:                     $entity->attributes = $this->parse(NULL, array());
205:                     $value = $entity;
206:                 } else {
207:                     $n++;
208:                     $value = $this->parse(NULL, array());
209:                 }
210:                 $hasValue = TRUE;
211:                 if (!isset($tokens[$n]) || $tokens[$n] !== self::$brackets[$t]) { // unexpected type of bracket or block-parser
212:                     $this->error();
213:                 }
214: 
215:             } elseif ($t === ']' || $t === '}' || $t === ')') { // Closing bracket ] ) }
216:                 if (!$inlineParser) {
217:                     $this->error();
218:                 }
219:                 break;
220: 
221:             } elseif ($t[0] === "\n") { // Indent
222:                 if ($inlineParser) {
223:                     if ($hasKey || $hasValue) {
224:                         $this->addValue($result, $hasKey, $key, $hasValue ? $value : NULL);
225:                         $hasKey = $hasValue = FALSE;
226:                     }
227: 
228:                 } else {
229:                     while (isset($tokens[$n+1]) && $tokens[$n+1][0] === "\n") $n++; // skip to last indent
230:                     if (!isset($tokens[$n+1])) {
231:                         break;
232:                     }
233: 
234:                     $newIndent = strlen($tokens[$n]) - 1;
235:                     if ($indent === NULL) { // first iteration
236:                         $indent = $newIndent;
237:                     }
238:                     if ($newIndent) {
239:                         if ($this->indentTabs === NULL) {
240:                             $this->indentTabs = $tokens[$n][1] === "\t";
241:                         }
242:                         if (strpos($tokens[$n], $this->indentTabs ? ' ' : "\t")) {
243:                             $n++;
244:                             $this->error('Either tabs or spaces may be used as indenting chars, but not both.');
245:                         }
246:                     }
247: 
248:                     if ($newIndent > $indent) { // open new block-array or hash
249:                         if ($hasValue || !$hasKey) {
250:                             $n++;
251:                             $this->error('Unexpected indentation.');
252:                         } else {
253:                             $this->addValue($result, $key !== NULL, $key, $this->parse($newIndent));
254:                         }
255:                         $newIndent = isset($tokens[$n]) ? strlen($tokens[$n]) - 1 : 0;
256:                         $hasKey = FALSE;
257: 
258:                     } else {
259:                         if ($hasValue && !$hasKey) { // block items must have "key"; NULL key means list item
260:                             break;
261: 
262:                         } elseif ($hasKey) {
263:                             $this->addValue($result, $key !== NULL, $key, $hasValue ? $value : NULL);
264:                             $hasKey = $hasValue = FALSE;
265:                         }
266:                     }
267: 
268:                     if ($newIndent < $indent) { // close block
269:                         return $result; // block parser exit point
270:                     }
271:                 }
272: 
273:             } else { // Value
274:                 if ($hasValue) {
275:                     $this->error();
276:                 }
277:                 static $consts = array(
278:                     'true' => TRUE, 'True' => TRUE, 'TRUE' => TRUE, 'yes' => TRUE, 'Yes' => TRUE, 'YES' => TRUE, 'on' => TRUE, 'On' => TRUE, 'ON' => TRUE,
279:                     'false' => FALSE, 'False' => FALSE, 'FALSE' => FALSE, 'no' => FALSE, 'No' => FALSE, 'NO' => FALSE, 'off' => FALSE, 'Off' => FALSE, 'OFF' => FALSE,
280:                 );
281:                 if ($t[0] === '"') {
282:                     $value = preg_replace_callback('#\\\\(?:u[0-9a-f]{4}|x[0-9a-f]{2}|.)#i', array($this, 'cbString'), substr($t, 1, -1));
283:                 } elseif ($t[0] === "'") {
284:                     $value = substr($t, 1, -1);
285:                 } elseif (isset($consts[$t])) {
286:                     $value = $consts[$t];
287:                 } elseif ($t === 'null' || $t === 'Null' || $t === 'NULL') {
288:                     $value = NULL;
289:                 } elseif (is_numeric($t)) {
290:                     $value = $t * 1;
291:                 } 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)) {
292:                     $value = new DateTime53($t);
293:                 } else { // literal
294:                     $value = $t;
295:                 }
296:                 $hasValue = TRUE;
297:             }
298:         }
299: 
300:         if ($inlineParser) {
301:             if ($hasKey || $hasValue) {
302:                 $this->addValue($result, $hasKey, $key, $hasValue ? $value : NULL);
303:             }
304:         } else {
305:             if ($hasValue && !$hasKey) { // block items must have "key"
306:                 if ($result === NULL) {
307:                     $result = $value; // simple value parser
308:                 } else {
309:                     $this->error();
310:                 }
311:             } elseif ($hasKey) {
312:                 $this->addValue($result, $key !== NULL, $key, $hasValue ? $value : NULL);
313:             }
314:         }
315:         return $result;
316:     }
317: 
318: 
319:     private function addValue(& $result, $hasKey, $key, $value)
320:     {
321:         if ($hasKey) {
322:             if ($result && array_key_exists($key, $result)) {
323:                 $this->error("Duplicated key '$key'");
324:             }
325:             $result[$key] = $value;
326:         } else {
327:             $result[] = $value;
328:         }
329:     }
330: 
331: 
332:     private function cbString($m)
333:     {
334:         static $mapping = array('t' => "\t", 'n' => "\n", 'r' => "\r", 'f' => "\x0C", 'b' => "\x08", '"' => '"', '\\' => '\\',  '/' => '/', '_' => "\xc2\xa0");
335:         $sq = $m[0];
336:         if (isset($mapping[$sq[1]])) {
337:             return $mapping[$sq[1]];
338:         } elseif ($sq[1] === 'u' && strlen($sq) === 6) {
339:             return Strings::chr(hexdec(substr($sq, 2)));
340:         } elseif ($sq[1] === 'x' && strlen($sq) === 4) {
341:             return chr(hexdec(substr($sq, 2)));
342:         } else {
343:             $this->error("Invalid escaping sequence $sq");
344:         }
345:     }
346: 
347: 
348:     private function error($message = "Unexpected '%s'")
349:     {
350:         list(, $line, $col) = self::$tokenizer->getOffset($this->n);
351:         $token = isset(self::$tokenizer->tokens[$this->n])
352:             ? str_replace("\n", '<new line>', Strings::truncate(self::$tokenizer->tokens[$this->n], 40))
353:             : 'end';
354:         throw new NeonException(str_replace('%s', $token, $message) . " on line $line, column $col.");
355:     }
356: 
357: }
358: 
359: 
360: /**
361:  * Representation of 'foo(bar=1)' literal
362:  * @package Nette\Utils
363:  */
364: class NeonEntity extends stdClass
365: {
366:     public $value;
367:     public $attributes;
368: }
369: 
370: 
371: /**
372:  * The exception that indicates error of NEON decoding.
373:  * @package Nette\Utils
374:  */
375: class NeonException extends Exception
376: {
377: }
378: 
Nette Framework 2.0.18 (for PHP 5.2, un-prefixed) API documentation generated by ApiGen 2.8.0