Packages

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

Classes

  • Arrays
  • CriticalSection
  • Finder
  • Html
  • Json
  • LimitedScope
  • MimeTypeDetector
  • Neon
  • Paginator
  • Strings

Exceptions

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