Namespaces

  • 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
  • PHP

Classes

  • Arrays
  • Finder
  • Html
  • Json
  • LimitedScope
  • MimeTypeDetector
  • Neon
  • NeonEntity
  • Paginator
  • Strings
  • Tokenizer
  • Validators

Exceptions

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