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

Classes

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

Exceptions

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