├── .gitignore ├── LICENSE ├── README.md ├── autoload.php.dist ├── phpdoc.ini.dist ├── phpunit.xml.dist ├── src └── Everzet │ └── Jade │ ├── Dumper │ ├── DumperInterface.php │ └── PHPDumper.php │ ├── Exception │ └── Exception.php │ ├── Filter │ ├── CDATAFilter.php │ ├── CSSFilter.php │ ├── FilterInterface.php │ ├── JavaScriptFilter.php │ └── PHPFilter.php │ ├── Jade.php │ ├── Lexer │ ├── Lexer.php │ └── LexerInterface.php │ ├── Node │ ├── BlockNode.php │ ├── CodeNode.php │ ├── CommentNode.php │ ├── DoctypeNode.php │ ├── FilterNode.php │ ├── Node.php │ ├── TagNode.php │ └── TextNode.php │ ├── Parser.php │ └── Visitor │ ├── AutotagsVisitor.php │ └── VisitorInterface.php ├── tests ├── Everzet │ └── Jade │ │ ├── FiltersTest.php │ │ └── JadeTest.php └── bootstrap.php └── vendor └── symfony └── src └── Symfony └── Framework └── UniversalClassLoader.php /.gitignore: -------------------------------------------------------------------------------- 1 | src/api 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2010 Konstantin Kudryashov 2 | 3 | Permission is hereby granted, free of charge, to any person 4 | obtaining a copy of this software and associated documentation 5 | files (the "Software"), to deal in the Software without 6 | restriction, including without limitation the rights to use, 7 | copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the 9 | Software is furnished to do so, subject to the following 10 | conditions: 11 | 12 | The above copyright notice and this permission notice shall be 13 | included in all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 17 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 18 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 19 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 20 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 21 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Jade - template compiler for PHP5.3 2 | 3 | *Jade* is a high performance template compiler heavily influenced by [Haml](http://haml-lang.com) 4 | and implemented for PHP 5.3. 5 | 6 | ## Features 7 | 8 | - high performance parser 9 | - great readability 10 | - contextual error reporting at compile & run time 11 | - html 5 mode (using the _!!! 5_ doctype) 12 | - combine dynamic and static tag classes 13 | - no tag prefix 14 | - clear & beautiful HTML output 15 | - filters 16 | - :php 17 | - :cdata 18 | - :css 19 | - :javascript 20 | - you even can write & add own filters throught API 21 | - [TextMate Bundle](http://github.com/miksago/jade-tmbundle) 22 | - [VIM Plugin](http://github.com/vim-scripts/jade.vim.git) 23 | 24 | ## Public API 25 | 26 | $dumper = new PHPDumper(); 27 | $dumper->registerVisitor('tag', new AutotagsVisitor()); 28 | $dumper->registerFilter('javascript', new JavaScriptFilter()); 29 | $dumper->registerFilter('cdata', new CDATAFilter()); 30 | $dumper->registerFilter('php', new PHPFilter()); 31 | $dumper->registerFilter('style', new CSSFilter()); 32 | 33 | // Initialize parser & Jade 34 | $parser = new Parser(new Lexer()); 35 | $jade = new Jade($parser, $dumper); 36 | 37 | // Parse a template (both string & file containers) 38 | echo $jade->render($template); 39 | 40 | ## Syntax 41 | 42 | ### Line Endings 43 | 44 | **CRLF** and **CR** are converted to **LF** before parsing. 45 | 46 | ### Indentation 47 | 48 | Jade is indentation based, however currently only supports a _2 space_ indent. 49 | 50 | ### Tags 51 | 52 | A tag is simply a leading word: 53 | 54 | html 55 | 56 | for example is converted to `` 57 | 58 | tags can also have ids: 59 | 60 | div#container 61 | 62 | which would render `
` 63 | 64 | how about some classes? 65 | 66 | div.user-details 67 | 68 | renders `
` 69 | 70 | multiple classes? _and_ an id? sure: 71 | 72 | div#foo.bar.baz 73 | 74 | renders `
` 75 | 76 | div div div sure is annoying, how about: 77 | 78 | #foo 79 | .bar 80 | 81 | which is syntactic sugar for what we have already been doing, and outputs: 82 | 83 |
84 | 85 | jade.php has a feature, called "autotags". It's just snippets for tags. Autotags will expand to basic tags with custom attributes. For example: 86 | 87 | input:text 88 | 89 | will expand to `` & it's the same as `input( type="text" )`, but shorter. 90 | Another examples: 91 | 92 | input:submit( value="Send" ) 93 | 94 | will become ``. 95 | 96 | You can even add you own autotags with: 97 | 98 | $parser->setAutotag('input:progress', 'input', array('type'=>'text', class=>'progress-bar')); 99 | 100 | that will expands to ``. 101 | 102 | It also supports new HTML5 tags (`input:email` => ``). 103 | 104 | ### Tag Text 105 | 106 | Simply place some content after the tag: 107 | 108 | p wahoo! 109 | 110 | renders `

wahoo!

`. 111 | 112 | well cool, but how about large bodies of text: 113 | 114 | p 115 | | foo bar baz 116 | | rawr rawr 117 | | super cool 118 | | go Jade go 119 | 120 | renders `

foo bar baz rawr.....

` 121 | 122 | Actually want `` for some reason? Use `{{}}` instead: 123 | 124 | p {{$something}} 125 | 126 | now we have `

` 127 | 128 | ### Nesting 129 | 130 | ul 131 | li one 132 | li two 133 | li three 134 | 135 | ### Attributes 136 | 137 | Jade currently supports '(' and ')' as attribute delimiters. 138 | 139 | a(href='/login', title='View login page') Login 140 | 141 | Alternatively we may use the colon to separate pairs: 142 | 143 | a(href: '/login', title: 'View login page') Login 144 | 145 | Boolean attributes are also supported: 146 | 147 | input(type="checkbox", checked) 148 | 149 | Boolean attributes with code will only output the attribute when `true`: 150 | 151 | input(type="checkbox", checked: someValue) 152 | 153 | Note: Leading / trailing whitespace is _ignore_ for attr pairs. 154 | 155 | ### Doctypes 156 | 157 | To add a doctype simply use `!!!` followed by an optional value: 158 | 159 | !!! 160 | 161 | Will output the _transitional_ doctype, however: 162 | 163 | !!! 5 164 | 165 | Will output html 5's doctype. Below are the doctypes 166 | defined by default, which can easily be extended: 167 | 168 | $doctypes = array( 169 | '5' => '', 170 | 'xml' => '', 171 | 'default' => '', 172 | 'transitional' => '', 173 | 'strict' => '', 174 | 'frameset' => '', 175 | '1.1' => '', 176 | 'basic' => '', 177 | 'mobile' => '' 178 | ); 179 | 180 | ## Comments 181 | 182 | ### Jade Comments 183 | 184 | Jade supports sharp comments (`//- COMMENT`). So jade block: 185 | 186 | //- JADE 187 | - $foo = "'; 32 | 33 | return $html; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/Everzet/Jade/Filter/PHPFilter.php: -------------------------------------------------------------------------------- 1 | 8 | * 9 | * For the full copyright and license information, please view the LICENSE 10 | * file that was distributed with this source code. 11 | */ 12 | 13 | /** 14 | * PHP tag filter. 15 | */ 16 | class PHPFilter implements FilterInterface 17 | { 18 | /** 19 | * Filter text. 20 | * 21 | * @param string $text text to filter 22 | * @param array $attributes filter options from template 23 | * @param string $indent indentation string 24 | * 25 | * @return string filtered text 26 | */ 27 | public function filter($text, array $attributes, $indent) 28 | { 29 | $html = $indent . ''; 32 | 33 | return $html; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/Everzet/Jade/Jade.php: -------------------------------------------------------------------------------- 1 | 12 | * 13 | * For the full copyright and license information, please view the LICENSE 14 | * file that was distributed with this source code. 15 | */ 16 | 17 | /** 18 | * Jade. 19 | */ 20 | class Jade 21 | { 22 | protected $parser; 23 | protected $dumper; 24 | protected $cache; 25 | 26 | /** 27 | * Initialize parser. 28 | * 29 | * @param LexerInterface $lexer jade lexer 30 | */ 31 | public function __construct(Parser $parser, DumperInterface $dumper, $cache = null) 32 | { 33 | $this->parser = $parser; 34 | $this->dumper = $dumper; 35 | $this->cache = $cache; 36 | } 37 | 38 | /** 39 | * Render provided input to dumped string. 40 | * 41 | * @param string $input input string or file path 42 | * 43 | * @return string dumped string 44 | */ 45 | public function render($input) 46 | { 47 | $source = $this->getInputSource($input); 48 | $parsed = $this->parser->parse($source); 49 | 50 | return $this->dumper->dump($parsed); 51 | } 52 | 53 | /** 54 | * Get current fresh cache path or render & dump input to new cache & return it's path. 55 | * 56 | * @param string $input input string or file path 57 | * 58 | * @return string cache path 59 | */ 60 | public function cache($input) 61 | { 62 | if (null === $this->cache || !is_dir($this->cache)) { 63 | throw new Exception('You must provide correct cache path to Jade for caching.'); 64 | } 65 | 66 | $cacheKey = $this->getInputCacheKey($input); 67 | $cacheTime = $this->getCacheTime($cacheKey); 68 | 69 | if (false !== $cacheTime && $this->isCacheFresh($input, $cacheTime)) { 70 | return $this->getCachePath($cacheKey); 71 | } 72 | 73 | if (!is_writable($this->cache)) { 74 | throw new Exception(sprintf('Cache directory must be writable. "%s" is not.', $this->cache)); 75 | } 76 | 77 | $rendered = $this->render($input); 78 | 79 | return $this->cacheInput($cacheKey, $rendered); 80 | } 81 | 82 | /** 83 | * Return source from input (Jade template). 84 | * 85 | * @param string $input input string or file path 86 | * 87 | * @return string 88 | */ 89 | protected function getInputSource($input) 90 | { 91 | if (is_file($input)) { 92 | return file_get_contents($input); 93 | } 94 | 95 | return (string) $input; 96 | } 97 | 98 | /** 99 | * Return caching key for input. 100 | * 101 | * @param string $input input string or file path 102 | * 103 | * @return string 104 | */ 105 | protected function getInputCacheKey($input) 106 | { 107 | if (is_file($input)) { 108 | return basename($input, '.jade'); 109 | } else { 110 | throw new \InvalidArgumentException('Only file templates can be cached.'); 111 | } 112 | } 113 | 114 | /** 115 | * Return full cache path for specified cache key. 116 | * 117 | * @param string $cacheKey cache key 118 | * 119 | * @return string absolute path 120 | */ 121 | protected function getCachePath($cacheKey) 122 | { 123 | return $this->cache . '/' . $cacheKey . '.php'; 124 | } 125 | 126 | /** 127 | * Return cache time creation. 128 | * 129 | * @param string $cacheKey cache key 130 | * 131 | * @return integer UNIX timestamp (filemtime used) 132 | */ 133 | protected function getCacheTime($cacheKey) 134 | { 135 | $path = $this->getCachePath($cacheKey); 136 | 137 | if (is_file($path)) { 138 | return filemtime($path); 139 | } 140 | 141 | return false; 142 | } 143 | 144 | /** 145 | * Return true if cache, created at specified time is fresh enough for provided input. 146 | * 147 | * @param string $input input string or file 148 | * @param srting $cacheTime cache key 149 | * 150 | * @return boolean true if fresh, false otherways 151 | */ 152 | protected function isCacheFresh($input, $cacheTime) 153 | { 154 | if (is_file($input)) { 155 | return filemtime($input) < $cacheTime; 156 | } 157 | 158 | return false; 159 | } 160 | 161 | /** 162 | * Cache rendered input at provided key. 163 | * 164 | * @param string $cacheKey new cache key 165 | * @param string $rendered rendered input 166 | * 167 | * @return string new cache path 168 | */ 169 | protected function cacheInput($cacheKey, $rendered) 170 | { 171 | $path = $this->getCachePath($cacheKey); 172 | 173 | file_put_contents($path, $rendered); 174 | 175 | return $path; 176 | } 177 | } 178 | -------------------------------------------------------------------------------- /src/Everzet/Jade/Lexer/Lexer.php: -------------------------------------------------------------------------------- 1 | 10 | * 11 | * For the full copyright and license information, please view the LICENSE 12 | * file that was distributed with this source code. 13 | */ 14 | 15 | /** 16 | * Jade Lexer. 17 | */ 18 | class Lexer implements LexerInterface 19 | { 20 | protected $input; 21 | protected $deferredObjects = array(); 22 | protected $lastIndents = 0; 23 | protected $lineno = 1; 24 | protected $stash = array(); 25 | 26 | /** 27 | * Set lexer input. 28 | * 29 | * @param string $input input string 30 | */ 31 | public function setInput($input) 32 | { 33 | $this->input = preg_replace(array('/\r\n|\r/', '/\t/'), array("\n", ' '), $input); 34 | $this->deferredObjects = array(); 35 | $this->lastIndents = 0; 36 | $this->lineno = 1; 37 | $this->stash = array(); 38 | } 39 | 40 | /** 41 | * Return next token or previously stashed one. 42 | * 43 | * @return Object 44 | */ 45 | public function getAdvancedToken() 46 | { 47 | if ($token = $this->getStashedToken()) { 48 | return $token; 49 | } 50 | 51 | return $this->getNextToken(); 52 | } 53 | 54 | /** 55 | * Return current line number. 56 | * 57 | * @return integer 58 | */ 59 | public function getCurrentLine() 60 | { 61 | return $this->lineno; 62 | } 63 | 64 | /** 65 | * Defer token. 66 | * 67 | * @param Object $token token to defer 68 | */ 69 | public function deferToken(\stdClass $token) 70 | { 71 | $this->deferredObjects[] = $token; 72 | } 73 | 74 | /** 75 | * Predict for number of tokens. 76 | * 77 | * @param integer $number number of tokens to predict 78 | * 79 | * @return Object predicted token 80 | */ 81 | public function predictToken($number = 1) 82 | { 83 | $fetch = $number - count($this->stash); 84 | 85 | while ($fetch-- > 0) { 86 | $this->stash[] = $this->getNextToken(); 87 | } 88 | 89 | return $this->stash[--$number]; 90 | } 91 | 92 | /** 93 | * Construct token with specified parameters. 94 | * 95 | * @param string $type token type 96 | * @param string $value token value 97 | * 98 | * @return Object new token object 99 | */ 100 | public function takeToken($type, $value = null) 101 | { 102 | return (Object) array( 103 | 'type' => $type 104 | , 'line' => $this->lineno 105 | , 'value' => $value 106 | ); 107 | } 108 | 109 | /** 110 | * Return stashed token. 111 | * 112 | * @return Object|boolean token if has stashed, false otherways 113 | */ 114 | protected function getStashedToken() 115 | { 116 | return count($this->stash) ? array_shift($this->stash) : null; 117 | } 118 | 119 | /** 120 | * Return deferred token. 121 | * 122 | * @return Object|boolean token if has deferred, false otherways 123 | */ 124 | protected function getDeferredToken() 125 | { 126 | return count($this->deferredObjects) ? array_shift($this->deferredObjects) : null; 127 | } 128 | 129 | /** 130 | * Return next token. 131 | * 132 | * @return Object 133 | */ 134 | protected function getNextToken() 135 | { 136 | $scanners = array( 137 | 'getDeferredToken' 138 | , 'scanEOS' 139 | , 'scanTag' 140 | , 'scanFilter' 141 | , 'scanCode' 142 | , 'scanDoctype' 143 | , 'scanId' 144 | , 'scanClass' 145 | , 'scanAttributes' 146 | , 'scanIndentation' 147 | , 'scanComment' 148 | , 'scanText' 149 | ); 150 | 151 | foreach ($scanners as $scan) { 152 | $token = $this->$scan(); 153 | 154 | if (null !== $token && $token) { 155 | return $token; 156 | } 157 | } 158 | } 159 | 160 | /** 161 | * Consume input. 162 | * 163 | * @param integer $length length of input to consume 164 | */ 165 | protected function consumeInput($length) 166 | { 167 | $this->input = mb_substr($this->input, $length); 168 | } 169 | 170 | /** 171 | * Scan for token with specified regex. 172 | * 173 | * @param string $regex regular expression 174 | * @param string $type expected token type 175 | * 176 | * @return Object|null 177 | */ 178 | protected function scanInput($regex, $type) 179 | { 180 | $matches = array(); 181 | if (preg_match($regex, $this->input, $matches)) { 182 | $this->consumeInput(mb_strlen($matches[0])); 183 | 184 | return $this->takeToken($type, $matches[1]); 185 | } 186 | } 187 | 188 | /** 189 | * Scan EOS from input & return it if found. 190 | * 191 | * @return Object|null 192 | */ 193 | protected function scanEOS() 194 | { 195 | if (mb_strlen($this->input)) { 196 | return; 197 | } 198 | 199 | return $this->lastIndents-- > 0 ? $this->takeToken('outdent') : $this->takeToken('eos'); 200 | } 201 | 202 | /** 203 | * Scan comment from input & return it if found. 204 | * 205 | * @return Object|null 206 | */ 207 | protected function scanComment() 208 | { 209 | $matches = array(); 210 | 211 | if (preg_match('/^ *\/\/(-)?([^\n]+)?/', $this->input, $matches)) { 212 | $this->consumeInput(mb_strlen($matches[0])); 213 | $token = $this->takeToken('comment', isset($matches[2]) ? $matches[2] : ''); 214 | $token->buffer = !isset($matches[1]) || '-' !== $matches[1]; 215 | 216 | return $token; 217 | } 218 | } 219 | 220 | /** 221 | * Scan tag from input & return it if found. 222 | * 223 | * @return Object|null 224 | */ 225 | protected function scanTag() 226 | { 227 | return $this->scanInput('/^(\w[:-\w]*)/', 'tag'); 228 | } 229 | 230 | /** 231 | * Scan tag from input & return it if found. 232 | * 233 | * @return Object|null 234 | */ 235 | protected function scanFilter() 236 | { 237 | return $this->scanInput('/^:(\w+)/', 'filter'); 238 | } 239 | 240 | /** 241 | * Scan doctype from input & return it if found. 242 | * 243 | * @return Object|null 244 | */ 245 | protected function scanDoctype() 246 | { 247 | return $this->scanInput('/^!!! *(\w+)?/', 'doctype'); 248 | } 249 | 250 | /** 251 | * Scan id from input & return it if found. 252 | * 253 | * @return Object|null 254 | */ 255 | protected function scanId() 256 | { 257 | return $this->scanInput('/^#([\w-]+)/', 'id'); 258 | } 259 | 260 | /** 261 | * Scan class from input & return it if found. 262 | * 263 | * @return Object|null 264 | */ 265 | protected function scanClass() 266 | { 267 | return $this->scanInput('/^\.([\w-]+)/', 'class'); 268 | } 269 | 270 | /** 271 | * Scan text from input & return it if found. 272 | * 273 | * @return Object|null 274 | */ 275 | protected function scanText() 276 | { 277 | return $this->scanInput('/^(?:\|)? ?([^\n]+)/', 'text'); 278 | } 279 | 280 | /** 281 | * Scan code from input & return it if found. 282 | * 283 | * @return Object|null 284 | */ 285 | protected function scanCode() 286 | { 287 | $matches = array(); 288 | 289 | if (preg_match('/^(!?=|-)([^\n]+)/', $this->input, $matches)) { 290 | $this->consumeInput(mb_strlen($matches[0])); 291 | 292 | $flags = $matches[1]; 293 | $token = $this->takeToken('code', $matches[2]); 294 | $token->buffer = (isset($flags[0]) && '=' === $flags[0]) || (isset($flags[1]) && '=' === $flags[1]); 295 | 296 | return $token; 297 | } 298 | } 299 | 300 | /** 301 | * Scan attributes from input & return them if found. 302 | * 303 | * @return Object|null 304 | */ 305 | protected function scanAttributes() 306 | { 307 | if ('(' === $this->input[0]) { 308 | $index = $this->getDelimitersIndex('(', ')'); 309 | $input = mb_substr($this->input, 1, $index - 1); 310 | $token = $this->takeToken('attributes', $input); 311 | $attributes = preg_split('/ *, *(?=[\'"\w-]+ *[:=]|[\w-]+ *$)/', $token->value); 312 | $this->consumeInput($index + 1); 313 | $token->attributes = array(); 314 | 315 | foreach ($attributes as $i => $pair) { 316 | $pair = preg_replace('/^ *| *$/', '', $pair); 317 | $colon = mb_strpos($pair, ':'); 318 | $equal = mb_strpos($pair, '='); 319 | 320 | $sbrac = mb_strpos($pair, '\''); 321 | $dbrac = mb_strpos($pair, '"'); 322 | if ($sbrac < 1) { 323 | $sbrac = false; 324 | } 325 | if ($dbrac < 1) { 326 | $dbrac = false; 327 | } 328 | if ((false !== $sbrac && $colon > $sbrac) || (false !== $dbrac && $colon > $dbrac)) { 329 | $colon = false; 330 | } 331 | if ((false !== $sbrac && $equal > $sbrac) || (false !== $dbrac && $equal > $dbrac)) { 332 | $equal = false; 333 | } 334 | 335 | if (false === $colon && false === $equal) { 336 | $key = $pair; 337 | $value = true; 338 | } else { 339 | $splitter = false !== $colon ? $colon : $equal; 340 | 341 | if (false !== $colon && $colon < $equal) { 342 | $splitter = $colon; 343 | } 344 | 345 | $key = mb_substr($pair, 0, $splitter); 346 | $value = preg_replace('/^ *[\'"]?|[\'"]? *$/', '', mb_substr($pair, ++$splitter, mb_strlen($pair))); 347 | 348 | if ('true' === $value) { 349 | $value = true; 350 | } elseif (empty($value) || 'null' === $value || 'false' === $value) { 351 | $value = false; 352 | } 353 | } 354 | 355 | $token->attributes[preg_replace(array('/^ +| +$/', '/^[\'"]|[\'"]$/'), '', $key)] = $value; 356 | } 357 | 358 | return $token; 359 | } 360 | } 361 | 362 | /** 363 | * Scan indentation from input & return it if found. 364 | * 365 | * @return Object|null 366 | */ 367 | protected function scanIndentation() 368 | { 369 | $matches = array(); 370 | 371 | if (preg_match('/^\n( *)/', $this->input, $matches)) { 372 | $this->lineno++; 373 | $this->consumeInput(mb_strlen($matches[0])); 374 | 375 | $token = $this->takeToken('indent', $matches[1]); 376 | $indents = mb_strlen($token->value) / 2; 377 | 378 | 379 | if (mb_strlen($this->input) && "\n" === $this->input[0]) { 380 | $token->type = 'newline'; 381 | 382 | return $token; 383 | } elseif (0 !== $indents % 1) { 384 | throw new Exception(sprintf( 385 | 'Invalid indentation found. Spaces count must be a multiple of two, but %d got.' 386 | , mb_strlen($token->value) 387 | )); 388 | } elseif ($this->lastIndents === $indents) { 389 | $token->type = 'newline'; 390 | } elseif ($this->lastIndents + 1 < $indents) { 391 | throw new Exception(sprintf( 392 | 'Invalid indentation found. Got %d, but expected %d.' 393 | , $indents 394 | , $this->lastIndents + 1 395 | )); 396 | } elseif ($this->lastIndents > $indents) { 397 | $count = $this->lastIndents - $indents; 398 | $token->type = 'outdent'; 399 | while (--$count) { 400 | $this->deferToken($this->takeToken('outdent')); 401 | } 402 | } 403 | 404 | $this->lastIndents = $indents; 405 | 406 | return $token; 407 | } 408 | } 409 | 410 | /** 411 | * Return the index of begin/end delimiters. 412 | * 413 | * @param string $begin befin delimiter 414 | * @param string $end end delimiter 415 | * 416 | * @return integer position index 417 | */ 418 | protected function getDelimitersIndex($begin, $end) 419 | { 420 | $string = $this->input; 421 | $nbegin = 0; 422 | $nend = 0; 423 | $position = 0; 424 | 425 | $sbrac = false; 426 | $dbrac = false; 427 | 428 | for ($i = 0, $length = mb_strlen($string); $i < $length; ++$i) { 429 | if ('"' === $string[$i]) { 430 | $dbrac = !$dbrac; 431 | } elseif ('\'' === $string[$i]) { 432 | $sbrac = !$sbrac; 433 | } 434 | 435 | if (!$sbrac && !$dbrac && $begin === $string[$i]) { 436 | ++$nbegin; 437 | } elseif (!$sbrac && !$dbrac && $end === $string[$i]) { 438 | if (++$nend === $nbegin) { 439 | $position = $i; 440 | break; 441 | } 442 | } 443 | } 444 | 445 | return $position; 446 | } 447 | } 448 | -------------------------------------------------------------------------------- /src/Everzet/Jade/Lexer/LexerInterface.php: -------------------------------------------------------------------------------- 1 | 8 | * 9 | * For the full copyright and license information, please view the LICENSE 10 | * file that was distributed with this source code. 11 | */ 12 | 13 | /** 14 | * Jade Lexer Interface. 15 | */ 16 | interface LexerInterface 17 | { 18 | /** 19 | * Set lexer input. 20 | * 21 | * @param string $input input string 22 | */ 23 | public function setInput($input); 24 | 25 | /** 26 | * Return next token or previously stashed one. 27 | * 28 | * @return Object 29 | */ 30 | public function getAdvancedToken(); 31 | 32 | /** 33 | * Return current line number. 34 | * 35 | * @return integer 36 | */ 37 | public function getCurrentLine(); 38 | 39 | /** 40 | * Defer token. 41 | * 42 | * @param Object $token token to defer 43 | */ 44 | public function deferToken(\stdClass $token); 45 | 46 | /** 47 | * Predict for number of tokens. 48 | * 49 | * @param integer $number number of tokens to predict 50 | * 51 | * @return Object predicted token 52 | */ 53 | public function predictToken($number = 1); 54 | 55 | /** 56 | * Construct token with specified parameters. 57 | * 58 | * @param string $type token type 59 | * @param string $value token value 60 | * 61 | * @return Object new token object 62 | */ 63 | public function takeToken($type, $value = null); 64 | } 65 | -------------------------------------------------------------------------------- /src/Everzet/Jade/Node/BlockNode.php: -------------------------------------------------------------------------------- 1 | 8 | * 9 | * For the full copyright and license information, please view the LICENSE 10 | * file that was distributed with this source code. 11 | */ 12 | 13 | /** 14 | * Block Node. 15 | */ 16 | class BlockNode extends Node 17 | { 18 | protected $childs = array(); 19 | 20 | /** 21 | * Add child node. 22 | * 23 | * @param Node $node child node 24 | */ 25 | public function addChild(Node $node) 26 | { 27 | $this->childs[] = $node; 28 | } 29 | 30 | /** 31 | * Return child nodes. 32 | * 33 | * @return array array of Node's 34 | */ 35 | public function getChilds() 36 | { 37 | return $this->childs; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/Everzet/Jade/Node/CodeNode.php: -------------------------------------------------------------------------------- 1 | 8 | * 9 | * For the full copyright and license information, please view the LICENSE 10 | * file that was distributed with this source code. 11 | */ 12 | 13 | /** 14 | * Code Node. 15 | */ 16 | class CodeNode extends Node 17 | { 18 | protected $code; 19 | protected $buffering = false; 20 | protected $block; 21 | 22 | /** 23 | * Initialize code node. 24 | * 25 | * @param string $code code string 26 | * @param boolean $buffering turn on buffering 27 | * @param integer $line source line 28 | */ 29 | public function __construct($code, $buffering = false, $line) 30 | { 31 | parent::__construct($line); 32 | 33 | $this->code = $code; 34 | $this->buffering = $buffering; 35 | } 36 | 37 | /** 38 | * Return code string. 39 | * 40 | * @return string 41 | */ 42 | public function getCode() 43 | { 44 | return $this->code; 45 | } 46 | 47 | /** 48 | * Return true if code buffered. 49 | * 50 | * @return boolean 51 | */ 52 | public function isBuffered() 53 | { 54 | return $this->buffering; 55 | } 56 | 57 | /** 58 | * Set block node. 59 | * 60 | * @param BlockNode $node child node 61 | */ 62 | public function setBlock(BlockNode $node) 63 | { 64 | $this->block = $node; 65 | } 66 | 67 | /** 68 | * Return block node. 69 | * 70 | * @return BlockNode 71 | */ 72 | public function getBlock() 73 | { 74 | return $this->block; 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/Everzet/Jade/Node/CommentNode.php: -------------------------------------------------------------------------------- 1 | 8 | * 9 | * For the full copyright and license information, please view the LICENSE 10 | * file that was distributed with this source code. 11 | */ 12 | 13 | /** 14 | * Comment Node. 15 | */ 16 | class CommentNode extends Node 17 | { 18 | protected $string; 19 | protected $buffering = false; 20 | protected $block; 21 | 22 | /** 23 | * Initialize code node. 24 | * 25 | * @param string $string comment string 26 | * @param boolean $buffering turn on buffering 27 | * @param integer $line source line 28 | */ 29 | public function __construct($string, $buffering = false, $line) 30 | { 31 | parent::__construct($line); 32 | 33 | $this->string = $string; 34 | $this->buffering = $buffering; 35 | } 36 | 37 | /** 38 | * Return comment string. 39 | * 40 | * @return string 41 | */ 42 | public function getString() 43 | { 44 | return $this->string; 45 | } 46 | 47 | /** 48 | * Return true if comment buffered. 49 | * 50 | * @return boolean 51 | */ 52 | public function isBuffered() 53 | { 54 | return $this->buffering; 55 | } 56 | 57 | /** 58 | * Set block node. 59 | * 60 | * @param BlockNode $node child node 61 | */ 62 | public function setBlock(BlockNode $node) 63 | { 64 | $this->block = $node; 65 | } 66 | 67 | /** 68 | * Return block node. 69 | * 70 | * @return BlockNode 71 | */ 72 | public function getBlock() 73 | { 74 | return $this->block; 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/Everzet/Jade/Node/DoctypeNode.php: -------------------------------------------------------------------------------- 1 | 8 | * 9 | * For the full copyright and license information, please view the LICENSE 10 | * file that was distributed with this source code. 11 | */ 12 | 13 | /** 14 | * Doctype Node. 15 | */ 16 | class DoctypeNode extends Node 17 | { 18 | protected $version; 19 | 20 | /** 21 | * Initialize doctype node. 22 | * 23 | * @param string $version doctype version 24 | * @param integer $line source line 25 | */ 26 | public function __construct($version, $line) 27 | { 28 | parent::__construct($line); 29 | 30 | $this->version = $version; 31 | } 32 | 33 | /** 34 | * Return doctype version. 35 | * 36 | * @return string 37 | */ 38 | public function getVersion() 39 | { 40 | return $this->version; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/Everzet/Jade/Node/FilterNode.php: -------------------------------------------------------------------------------- 1 | 8 | * 9 | * For the full copyright and license information, please view the LICENSE 10 | * file that was distributed with this source code. 11 | */ 12 | 13 | /** 14 | * Filter Node. 15 | */ 16 | class FilterNode extends Node 17 | { 18 | protected $name; 19 | protected $attributes = array(); 20 | protected $block; 21 | 22 | /** 23 | * Initialize Filter node. 24 | * 25 | * @param string $name filter name 26 | * @param array $attributes filter attributes 27 | * @param integer $line source line 28 | */ 29 | public function __construct($name, array $attributes = array(), $line) 30 | { 31 | parent::__construct($line); 32 | 33 | $this->name = $name; 34 | $this->attributes = $attributes; 35 | } 36 | 37 | /** 38 | * Set block node to filter. 39 | * 40 | * @param BlockNode|TextNode $node filtering node 41 | */ 42 | public function setBlock(Node $node) 43 | { 44 | $this->block = $node; 45 | } 46 | 47 | /** 48 | * Return block node to filter. 49 | * 50 | * @return BlockNode|TextNode 51 | */ 52 | public function getBlock() 53 | { 54 | return $this->block; 55 | } 56 | 57 | /** 58 | * Return filter name. 59 | * 60 | * @return string 61 | */ 62 | public function getName() 63 | { 64 | return $this->name; 65 | } 66 | 67 | /** 68 | * Return attributes array 69 | * 70 | * @return array associative array of attributes 71 | */ 72 | public function getAttributes() 73 | { 74 | return $this->attributes; 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/Everzet/Jade/Node/Node.php: -------------------------------------------------------------------------------- 1 | 8 | * 9 | * For the full copyright and license information, please view the LICENSE 10 | * file that was distributed with this source code. 11 | */ 12 | 13 | /** 14 | * Node. 15 | */ 16 | abstract class Node 17 | { 18 | protected $line; 19 | 20 | /** 21 | * Initialize node. 22 | * 23 | * @param integer $line source line 24 | */ 25 | public function __construct($line) 26 | { 27 | $this->line = $line; 28 | } 29 | 30 | /** 31 | * Return node source line. 32 | * 33 | * @return integer 34 | */ 35 | public function getLine() 36 | { 37 | return $this->line; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/Everzet/Jade/Node/TagNode.php: -------------------------------------------------------------------------------- 1 | 8 | * 9 | * For the full copyright and license information, please view the LICENSE 10 | * file that was distributed with this source code. 11 | */ 12 | 13 | /** 14 | * Tag Node. 15 | */ 16 | class TagNode extends BlockNode 17 | { 18 | protected $name; 19 | protected $attributes = array('id' => false); 20 | protected $text; 21 | protected $code; 22 | 23 | /** 24 | * Initialize tag node. 25 | * 26 | * @param string $name tag name 27 | * @param integer $line source line 28 | */ 29 | public function __construct($name, $line) 30 | { 31 | parent::__construct($line); 32 | 33 | $this->name = $name; 34 | } 35 | 36 | /** 37 | * Set tag name. 38 | * 39 | * @param string $name tag name 40 | */ 41 | public function setName($name) 42 | { 43 | $this->name = $name; 44 | } 45 | 46 | /** 47 | * Return tag name. 48 | * 49 | * @return string 50 | */ 51 | public function getName() 52 | { 53 | return $this->name; 54 | } 55 | 56 | /** 57 | * Set tag attribute to value. 58 | * 59 | * @param string $key attribute name 60 | * @param string $value attribute value 61 | */ 62 | public function setAttribute($key, $value) 63 | { 64 | if ('class' === $key) { 65 | if (!isset($this->attributes[$key])) { 66 | $this->attributes[$key] = array(); 67 | } 68 | 69 | $this->attributes[$key][] = $value; 70 | } else { 71 | $this->attributes[$key] = $value; 72 | } 73 | } 74 | 75 | /** 76 | * Return all attributes. 77 | * 78 | * @return array 79 | */ 80 | public function getAttributes() 81 | { 82 | return $this->attributes; 83 | } 84 | 85 | /** 86 | * Set inner text node. 87 | * 88 | * @param TextNode $node inner text 89 | */ 90 | public function setText(TextNode $node) 91 | { 92 | $this->text = $node; 93 | } 94 | 95 | /** 96 | * Return inner text node. 97 | * 98 | * @return TextNode|null 99 | */ 100 | public function getText() 101 | { 102 | return $this->text; 103 | } 104 | 105 | /** 106 | * Set inner code node. 107 | * 108 | * @param CodeNode $node inner code 109 | */ 110 | public function setCode(CodeNode $node) 111 | { 112 | $this->code = $node; 113 | } 114 | 115 | /** 116 | * Return inner code node. 117 | * 118 | * @return CodeNode 119 | */ 120 | public function getCode() 121 | { 122 | return $this->code; 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /src/Everzet/Jade/Node/TextNode.php: -------------------------------------------------------------------------------- 1 | 8 | * 9 | * For the full copyright and license information, please view the LICENSE 10 | * file that was distributed with this source code. 11 | */ 12 | 13 | /** 14 | * Text Node. 15 | */ 16 | class TextNode extends Node 17 | { 18 | protected $lines = array(); 19 | 20 | /** 21 | * Initialize text node with string. 22 | * 23 | * @param string|null $string text 24 | * @param integer $line source line 25 | */ 26 | public function __construct($string = null, $line) 27 | { 28 | parent::__construct($line); 29 | 30 | if (!empty($string)) { 31 | $this->lines = explode("\n", $string); 32 | } 33 | } 34 | 35 | /** 36 | * Add text line to node. 37 | * 38 | * @param string $line string line 39 | */ 40 | public function addLine($line) 41 | { 42 | $this->lines[] = $line; 43 | } 44 | 45 | /** 46 | * Return text lines. 47 | * 48 | * @return array array of strings 49 | */ 50 | public function getLines() 51 | { 52 | return $this->lines; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/Everzet/Jade/Parser.php: -------------------------------------------------------------------------------- 1 | 20 | * 21 | * For the full copyright and license information, please view the LICENSE 22 | * file that was distributed with this source code. 23 | */ 24 | 25 | /** 26 | * Jade Parser. 27 | */ 28 | class Parser 29 | { 30 | protected $lexer; 31 | 32 | /** 33 | * Initialize Parser. 34 | * 35 | * @param LexerInterface $lexer lexer object 36 | */ 37 | public function __construct(LexerInterface $lexer) 38 | { 39 | $this->lexer = $lexer; 40 | } 41 | 42 | /** 43 | * Parse input returning block node. 44 | * 45 | * @param string $input jade document 46 | * 47 | * @return BlockNode 48 | */ 49 | public function parse($input) 50 | { 51 | $this->lexer->setInput($input); 52 | 53 | $node = new BlockNode($this->lexer->getCurrentLine()); 54 | 55 | while ('eos' !== $this->lexer->predictToken()->type) { 56 | if ('newline' === $this->lexer->predictToken()->type) { 57 | $this->lexer->getAdvancedToken(); 58 | } else { 59 | $node->addChild($this->parseExpression()); 60 | } 61 | } 62 | 63 | return $node; 64 | } 65 | 66 | /** 67 | * Expect given type or throw Exception. 68 | * 69 | * @param string $type type 70 | */ 71 | protected function expectTokenType($type) 72 | { 73 | if ($type === $this->lexer->predictToken()->type) { 74 | return $this->lexer->getAdvancedToken(); 75 | } else { 76 | throw new Exception(sprintf('Expected %s, but got %s', $type, $this->lexer->predictToken()->type)); 77 | } 78 | } 79 | 80 | /** 81 | * Accept given type. 82 | * 83 | * @param string $type type 84 | */ 85 | protected function acceptTokenType($type) 86 | { 87 | if ($type === $this->lexer->predictToken()->type) { 88 | return $this->lexer->getAdvancedToken(); 89 | } 90 | } 91 | 92 | /** 93 | * Parse current expression & return Node. 94 | * 95 | * @return Node 96 | */ 97 | protected function parseExpression() 98 | { 99 | switch ($this->lexer->predictToken()->type) { 100 | case 'tag': 101 | return $this->parseTag(); 102 | case 'doctype': 103 | return $this->parseDoctype(); 104 | case 'filter': 105 | return $this->parseFilter(); 106 | case 'comment': 107 | return $this->parseComment(); 108 | case 'text': 109 | return $this->parseText(); 110 | case 'code': 111 | return $this->parseCode(); 112 | case 'id': 113 | case 'class': 114 | $token = $this->lexer->getAdvancedToken(); 115 | $this->lexer->deferToken($this->lexer->takeToken('tag', 'div')); 116 | $this->lexer->deferToken($token); 117 | 118 | return $this->parseExpression(); 119 | } 120 | } 121 | 122 | /** 123 | * Parse next text token. 124 | * 125 | * @return TextNode 126 | */ 127 | protected function parseText($trim = false) 128 | { 129 | $token = $this->expectTokenType('text'); 130 | $value = $trim ? preg_replace('/^ +/', '', $token->value) : $token->value; 131 | 132 | return new TextNode($value, $this->lexer->getCurrentLine()); 133 | } 134 | 135 | /** 136 | * Parse next code token. 137 | * 138 | * @return CodeNode 139 | */ 140 | protected function parseCode() 141 | { 142 | $token = $this->expectTokenType('code'); 143 | $node = new CodeNode($token->value, $token->buffer, $this->lexer->getCurrentLine()); 144 | 145 | // Skip newlines 146 | while ('newline' === $this->lexer->predictToken()->type) { 147 | $this->lexer->getAdvancedToken(); 148 | } 149 | 150 | if ('indent' === $this->lexer->predictToken()->type) { 151 | $node->setBlock($this->parseBlock()); 152 | } 153 | 154 | return $node; 155 | } 156 | 157 | /** 158 | * Parse next commend token. 159 | * 160 | * @return CommentNode 161 | */ 162 | protected function parseComment() 163 | { 164 | $token = $this->expectTokenType('comment'); 165 | $node = new CommentNode(preg_replace('/^ +| +$/', '', $token->value), $token->buffer, $this->lexer->getCurrentLine()); 166 | 167 | // Skip newlines 168 | while ('newline' === $this->lexer->predictToken()->type) { 169 | $this->lexer->getAdvancedToken(); 170 | } 171 | 172 | if ('indent' === $this->lexer->predictToken()->type) { 173 | $node->setBlock($this->parseBlock()); 174 | } 175 | 176 | return $node; 177 | } 178 | 179 | /** 180 | * Parse next doctype token. 181 | * 182 | * @return DoctypeNode 183 | */ 184 | protected function parseDoctype() 185 | { 186 | $token = $this->expectTokenType('doctype'); 187 | 188 | return new DoctypeNode($token->value, $this->lexer->getCurrentLine()); 189 | } 190 | 191 | /** 192 | * Parse next filter token. 193 | * 194 | * @return FilterNode 195 | */ 196 | protected function parseFilter() 197 | { 198 | $block = null; 199 | $token = $this->expectTokenType('filter'); 200 | $attributes = $this->acceptTokenType('attributes'); 201 | 202 | if ('text' === $this->lexer->predictToken(2)->type) { 203 | $block = $this->parseTextBlock(); 204 | } else { 205 | $block = $this->parseBlock(); 206 | } 207 | 208 | $node = new FilterNode( 209 | $token->value, null !== $attributes ? $attributes->attributes : array(), $this->lexer->getCurrentLine() 210 | ); 211 | $node->setBlock($block); 212 | 213 | return $node; 214 | } 215 | 216 | /** 217 | * Parse next indented? text token. 218 | * 219 | * @return TextToken 220 | */ 221 | protected function parseTextBlock() 222 | { 223 | $node = new TextNode(null, $this->lexer->getCurrentLine()); 224 | 225 | $this->expectTokenType('indent'); 226 | while ('text' === $this->lexer->predictToken()->type || 'newline' === $this->lexer->predictToken()->type) { 227 | if ('newline' === $this->lexer->predictToken()->type) { 228 | $this->lexer->getAdvancedToken(); 229 | } else { 230 | $node->addLine($this->lexer->getAdvancedToken()->value); 231 | } 232 | } 233 | $this->expectTokenType('outdent'); 234 | 235 | return $node; 236 | } 237 | 238 | /** 239 | * Parse indented block token. 240 | * 241 | * @return BlockNode 242 | */ 243 | protected function parseBlock() 244 | { 245 | $node = new BlockNode($this->lexer->getCurrentLine()); 246 | 247 | $this->expectTokenType('indent'); 248 | while ('outdent' !== $this->lexer->predictToken()->type) { 249 | if ('newline' === $this->lexer->predictToken()->type) { 250 | $this->lexer->getAdvancedToken(); 251 | } else { 252 | $node->addChild($this->parseExpression()); 253 | } 254 | } 255 | $this->expectTokenType('outdent'); 256 | 257 | return $node; 258 | } 259 | 260 | /** 261 | * Parse tag token. 262 | * 263 | * @return TagNode 264 | */ 265 | protected function parseTag() 266 | { 267 | $name = $this->lexer->getAdvancedToken()->value; 268 | $node = new TagNode($name, $this->lexer->getCurrentLine()); 269 | 270 | // Parse id, class, attributes token 271 | while (true) { 272 | switch ($this->lexer->predictToken()->type) { 273 | case 'id': 274 | case 'class': 275 | $token = $this->lexer->getAdvancedToken(); 276 | $node->setAttribute($token->type, $token->value); 277 | continue; 278 | case 'attributes': 279 | foreach ($this->lexer->getAdvancedToken()->attributes as $name => $value) { 280 | $node->setAttribute($name, $value); 281 | } 282 | continue; 283 | default: 284 | break(2); 285 | } 286 | } 287 | 288 | // Parse text/code token 289 | switch ($this->lexer->predictToken()->type) { 290 | case 'text': 291 | $node->setText($this->parseText(true)); 292 | break; 293 | case 'code': 294 | $node->setCode($this->parseCode()); 295 | break; 296 | } 297 | 298 | // Skip newlines 299 | while ('newline' === $this->lexer->predictToken()->type) { 300 | $this->lexer->getAdvancedToken(); 301 | } 302 | 303 | // Tag text on newline 304 | if ('text' === $this->lexer->predictToken()->type) { 305 | if ($text = $node->getText()) { 306 | $text->addLine(''); 307 | } else { 308 | $node->setText(new TextNode('', $this->lexer->getCurrentLine())); 309 | } 310 | } 311 | 312 | // Parse block indentation 313 | if ('indent' === $this->lexer->predictToken()->type) { 314 | $node->addChild($this->parseBlock()); 315 | } 316 | 317 | return $node; 318 | } 319 | } 320 | -------------------------------------------------------------------------------- /src/Everzet/Jade/Visitor/AutotagsVisitor.php: -------------------------------------------------------------------------------- 1 | 11 | * 12 | * For the full copyright and license information, please view the LICENSE 13 | * file that was distributed with this source code. 14 | */ 15 | 16 | /** 17 | * Autotags Replacer. 18 | */ 19 | class AutotagsVisitor implements VisitorInterface 20 | { 21 | protected $autotags = array( 22 | 'a:void' => array('tag' => 'a', 'attrs' => array('href' => 'javascript:void(0)')), 23 | 'form:post' => array('tag' => 'form', 'attrs' => array('method' => 'POST')), 24 | 'link:css' => array('tag' => 'link', 'attrs' => array('rel' => 'stylesheet', 'type' => 'text/css')), 25 | 'script:js' => array('tag' => 'script', 'attrs' => array('type' => 'text/javascript')), 26 | 'input:button' => array('tag' => 'input', 'attrs' => array('type' => 'button')), 27 | 'input:checkbox' => array('tag' => 'input', 'attrs' => array('type' => 'checkbox')), 28 | 'input:file' => array('tag' => 'input', 'attrs' => array('type' => 'file')), 29 | 'input:hidden' => array('tag' => 'input', 'attrs' => array('type' => 'hidden')), 30 | 'input:image' => array('tag' => 'input', 'attrs' => array('type' => 'image')), 31 | 'input:password' => array('tag' => 'input', 'attrs' => array('type' => 'password')), 32 | 'input:radio' => array('tag' => 'input', 'attrs' => array('type' => 'radio')), 33 | 'input:reset' => array('tag' => 'input', 'attrs' => array('type' => 'reset')), 34 | 'input:submit' => array('tag' => 'input', 'attrs' => array('type' => 'submit')), 35 | 'input:text' => array('tag' => 'input', 'attrs' => array('type' => 'text')), 36 | 'input:search' => array('tag' => 'input', 'attrs' => array('type' => 'search')), 37 | 'input:tel' => array('tag' => 'input', 'attrs' => array('type' => 'tel')), 38 | 'input:url' => array('tag' => 'input', 'attrs' => array('type' => 'url')), 39 | 'input:email' => array('tag' => 'input', 'attrs' => array('type' => 'email')), 40 | 'input:datetime' => array('tag' => 'input', 'attrs' => array('type' => 'datetime')), 41 | 'input:date' => array('tag' => 'input', 'attrs' => array('type' => 'date')), 42 | 'input:month' => array('tag' => 'input', 'attrs' => array('type' => 'month')), 43 | 'input:week' => array('tag' => 'input', 'attrs' => array('type' => 'week')), 44 | 'input:time' => array('tag' => 'input', 'attrs' => array('type' => 'time')), 45 | 'input:number' => array('tag' => 'input', 'attrs' => array('type' => 'number')), 46 | 'input:range' => array('tag' => 'input', 'attrs' => array('type' => 'range')), 47 | 'input:color' => array('tag' => 'input', 'attrs' => array('type' => 'color')), 48 | 'input:datetime-local' => array('tag' => 'input', 'attrs' => array('type' => 'datetime-local')) 49 | ); 50 | 51 | /** 52 | * Visit node. 53 | * 54 | * @param Node $node node to visit 55 | */ 56 | public function visit(Node $node) 57 | { 58 | if (!($node instanceof TagNode)) { 59 | throw new \InvalidArgumentException(sprintf('Autotags filter may only work with tag nodes, but %s given', get_class($node))); 60 | } 61 | 62 | if (isset($this->autotags[$node->getName()])) { 63 | foreach ($this->autotags[$node->getName()]['attrs'] as $key => $value) { 64 | $node->setAttribute($key, $value); 65 | } 66 | 67 | $node->setName($this->autotags[$node->getName()]['tag']); 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/Everzet/Jade/Visitor/VisitorInterface.php: -------------------------------------------------------------------------------- 1 | 10 | * 11 | * For the full copyright and license information, please view the LICENSE 12 | * file that was distributed with this source code. 13 | */ 14 | 15 | /** 16 | * Node Visitor Interface. 17 | */ 18 | interface VisitorInterface 19 | { 20 | /** 21 | * Visit node. 22 | * 23 | * @param Node $node node to visit 24 | */ 25 | public function visit(Node $node); 26 | } 27 | -------------------------------------------------------------------------------- /tests/Everzet/Jade/FiltersTest.php: -------------------------------------------------------------------------------- 1 | 17 | * 18 | * For the full copyright and license information, please view the LICENSE 19 | * file that was distributed with this source code. 20 | */ 21 | 22 | /** 23 | * Filters test 24 | */ 25 | class FiltersTest extends \PHPUnit_Framework_TestCase 26 | { 27 | protected $jade; 28 | 29 | public function __construct() 30 | { 31 | $parser = new Parser(new Lexer()); 32 | $dumper = new PHPDumper(); 33 | $dumper->registerVisitor('tag', new AutotagsVisitor()); 34 | $dumper->registerFilter('javascript', new JavaScriptFilter()); 35 | $dumper->registerFilter('cdata', new CDATAFilter()); 36 | $dumper->registerFilter('php', new PHPFilter()); 37 | $dumper->registerFilter('style', new CSSFilter()); 38 | 39 | $this->jade = new Jade($parser, $dumper); 40 | } 41 | 42 | protected function parse($value) 43 | { 44 | return $this->jade->render($value); 45 | } 46 | 47 | public function testFilterCodeInsertion() 48 | { 49 | $this->assertEquals( 50 | "", 51 | $this->parse(<<assertEquals( 62 | "", 63 | $this->parse(<<assertEquals( 70 | "", 71 | $this->parse(<<assertEquals( 79 | "\n

something else

", 80 | $this->parse(<<assertEquals( 93 | "", 94 | $this->parse(<<assertEquals( 105 | "", 106 | $this->parse(<<assertEquals( 115 | "", 116 | $this->parse(<< 134 |

135 | 136 | 141 |

142 | 143 | HTML; 144 | $this->assertEquals($html, $this->parse($jade)); 145 | 146 | $jade = << 159 |

160 | 161 | 166 |

167 |

168 | 169 |

170 | 171 | HTML; 172 | $this->assertEquals($html, $this->parse($jade)); 173 | 174 | $jade = << 188 | 196 | 199 | 200 | HTML; 201 | $this->assertEquals($html, $this->parse($jade)); 202 | } 203 | 204 | public function testPHPFilter() 205 | { 206 | $this->assertEquals( 207 | "", 208 | $this->parse(<< 12 | * 13 | * For the full copyright and license information, please view the LICENSE 14 | * file that was distributed with this source code. 15 | */ 16 | 17 | /** 18 | * Parser/Dumper test. 19 | */ 20 | class JadeTest extends \PHPUnit_Framework_TestCase 21 | { 22 | protected $jade; 23 | 24 | public function __construct() 25 | { 26 | $parser = new Parser(new Lexer()); 27 | $dumper = new PHPDumper(); 28 | $dumper->registerVisitor('tag', new AutotagsVisitor()); 29 | 30 | $this->jade = new Jade($parser, $dumper); 31 | } 32 | 33 | protected function parse($value) 34 | { 35 | return $this->jade->render($value); 36 | } 37 | 38 | public function testDoctypes() 39 | { 40 | $this->assertEquals('' , $this->parse('!!! xml')); 41 | $this->assertEquals('' , $this->parse('!!! 5')); 42 | } 43 | 44 | public function testLineEndings() 45 | { 46 | $tags = array('p', 'div', 'img'); 47 | $html = implode("\n", array('

', '
', '')); 48 | 49 | $this->assertEquals($html, $this->parse(implode("\r\n", $tags))); 50 | $this->assertEquals($html, $this->parse(implode("\r", $tags))); 51 | $this->assertEquals($html, $this->parse(implode("\n", $tags))); 52 | } 53 | 54 | public function testSingleQuotes() 55 | { 56 | $this->assertEquals("

'foo'

", $this->parse("p 'foo'")); 57 | $this->assertEquals("

\n 'foo'\n

", $this->parse("p\n | 'foo'")); 58 | $this->assertEquals(<< 60 | 61 | HTML 62 | , $this->parse(<<

', '
', '')); 73 | 74 | $this->assertEquals($html, $this->parse($str), 'Test basic tags'); 75 | $this->assertEquals('
', 76 | $this->parse('#item.something'), 'Test classes'); 77 | $this->assertEquals('
', $this->parse('div.something'), 78 | 'Test classes'); 79 | $this->assertEquals('
', $this->parse('div#something'), 80 | 'Test ids'); 81 | $this->assertEquals('
', $this->parse('.something'), 82 | 'Test stand-alone classes'); 83 | $this->assertEquals('
', $this->parse('#something'), 84 | 'Test stand-alone ids'); 85 | $this->assertEquals('
', $this->parse('#foo.bar')); 86 | $this->assertEquals('
', $this->parse('.bar#foo')); 87 | $this->assertEquals('
', 88 | $this->parse('div#foo(class="bar")')); 89 | $this->assertEquals('
', 90 | $this->parse('div(class="bar")#foo')); 91 | $this->assertEquals('
', 92 | $this->parse('div(id="bar").foo')); 93 | $this->assertEquals('
', $this->parse('div.foo.bar.baz')); 94 | $this->assertEquals('
', 95 | $this->parse('div(class="foo").bar.baz')); 96 | $this->assertEquals('
', 97 | $this->parse('div.foo(class="bar").baz')); 98 | $this->assertEquals('
', 99 | $this->parse('div.foo.bar(class="baz")')); 100 | $this->assertEquals('
', 101 | $this->parse('div.a-b2')); 102 | $this->assertEquals('
', 103 | $this->parse('div.a_b2')); 104 | $this->assertEquals('', 105 | $this->parse('fb:user')); 106 | } 107 | 108 | public function testNestedTags() 109 | { 110 | $jade = << 122 |
  • a
  • 123 |
  • b
  • 124 |
  • 125 |
      126 |
    • c
    • 127 |
    • d
    • 128 |
    129 |
  • 130 |
  • e
  • 131 | 132 | HTML; 133 | $this->assertEquals($html, $this->parse($jade)); 134 | 135 | $jade = << 142 | foo 143 | bar 144 | baz 145 | 146 | HTML; 147 | $this->assertEquals($html, $this->parse($jade)); 148 | 149 | $jade = << 157 |
  • one
  • 158 |
      159 | two 160 |
    • three
    • 161 |
    162 | 163 | HTML; 164 | $this->assertEquals($html, $this->parse($jade)); 165 | 166 | $jade = << 172 |
    173 | 174 | HTML; 175 | $this->assertEquals($html, $this->parse($jade)); 176 | } 177 | 178 | public function testVariableLengthNewlines() 179 | { 180 | $jade = << 196 |
  • a
  • 197 |
  • b
  • 198 |
  • 199 |
      200 |
    • c
    • 201 |
    • d
    • 202 |
    203 |
  • 204 |
  • e
  • 205 | 206 | HTML; 207 | $this->assertEquals($html, $this->parse($jade)); 208 | } 209 | 210 | public function testNewlines() 211 | { 212 | $jade = << 232 |
  • a
  • 233 |
  • b
  • 234 |
  • 235 |
      236 |
    • c
    • 237 |
    • d
    • 238 |
    239 |
  • 240 |
  • e
  • 241 | 242 | HTML; 243 | $this->assertEquals($html, $this->parse($jade)); 244 | 245 | $jade = << 252 |
  • 253 | visit 254 | foo 255 |
  • 256 | 257 | HTML; 258 | $this->assertEquals($html, $this->parse($jade)); 259 | } 260 | 261 | public function testTagText() 262 | { 263 | $this->assertEquals('some random text', $this->parse('| some random text')); 264 | $this->assertEquals('

    some random text

    ', $this->parse('p some random text')); 265 | } 266 | 267 | public function testTagTextBlock() 268 | { 269 | $this->assertEquals("

    \n foo\n bar\n baz\n

    ", $this->parse("p\n | foo\n | bar\n | baz")); 270 | $this->assertEquals("", $this->parse("label\n | Password:\n input")); 271 | } 272 | 273 | public function testTagTextCodeInsertion() 274 | { 275 | $this->assertEquals('yo, is cool', $this->parse('| yo, is cool')); 276 | $this->assertEquals('

    yo, is cool

    ', $this->parse('p yo, is cool')); 277 | $this->assertEquals('

    yo, is cool

    ', $this->parse('p yo, is cool')); 278 | $this->assertEquals('yo, is cool', $this->parse('| yo, is cool')); 279 | } 280 | 281 | public function testHtml5Mode() 282 | { 283 | $this->assertEquals("\n", $this->parse("!!! 5\ninput(type=\"checkbox\", checked)")); 284 | $this->assertEquals("\n", $this->parse("!!! 5\ninput(type=\"checkbox\", checked: true)")); 285 | $this->assertEquals("\n", $this->parse("!!! 5\ninput(type=\"checkbox\", checked: false)")); 286 | } 287 | 288 | public function testAttrs() 289 | { 290 | $this->assertEquals('', $this->parse('img(src="', $this->parse('script:js( charset = "UTF8", src:"http://stats.test.com/scripts/stat.js" )')); 643 | } 644 | 645 | public function testHTMLComments() 646 | { 647 | $jade = << 654 | 655 | I like sandwiches! 656 | 657 | HTML; 658 | $this->assertEquals($html, $this->parse($jade)); 659 | 660 | $jade = <<This doesn't render...

    669 |
    670 |

    Because it's commented out!

    671 |
    672 | --> 673 | HTML; 674 | 675 | $this->assertEquals($html, $this->parse($jade)); 676 | } 677 | 678 | public function testHTMLConditionalComments() 679 | { 680 | $jade = << 687 | 688 |

    Get Firefox

    689 |
    690 | 691 | HTML; 692 | $this->assertEquals($html, $this->parse($jade)); 693 | 694 | $jade = << 719 | 720 | 721 | 722 | 723 | 724 | 725 | 726 | 727 | 730 | 735 | 736 | 737 | HTML; 738 | $this->assertEquals($html, $this->parse($jade)); 739 | 740 | $jade = << 746 | 747 | 748 | HTML; 749 | $this->assertEquals($html, $this->parse($jade)); 750 | } 751 | 752 | public function testJSLinkTag() 753 | { 754 | $this->assertEquals('', $this->parse('a:void( value="hello" )')); 755 | } 756 | } 757 | -------------------------------------------------------------------------------- /tests/bootstrap.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | if (is_file(__DIR__.'/../autoload.php')) { 12 | require_once __DIR__ . '/../autoload.php'; 13 | } else { 14 | require_once __DIR__ . '/../autoload.php.dist'; 15 | } 16 | -------------------------------------------------------------------------------- /vendor/symfony/src/Symfony/Framework/UniversalClassLoader.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | /** 15 | * UniversalClassLoader implements a "universal" autoloader for PHP 5.3. 16 | * 17 | * It is able to load classes that use either: 18 | * 19 | * * The technical interoperability standards for PHP 5.3 namespaces and 20 | * class names (http://groups.google.com/group/php-standards/web/psr-0-final-proposal); 21 | * 22 | * * The PEAR naming convention for classes (http://pear.php.net/). 23 | * 24 | * Classes from a sub-namespace or a sub-hierarchy of PEAR classes can be 25 | * looked for in a list of locations to ease the vendoring of a sub-set of 26 | * classes for large projects. 27 | * 28 | * Example usage: 29 | * 30 | * $loader = new UniversalClassLoader(); 31 | * 32 | * // register classes with namespaces 33 | * $loader->registerNamespaces(array( 34 | * 'Symfony\Components' => __DIR__.'/components', 35 | * 'Symfony' => __DIR__.'/framework', 36 | * )); 37 | * 38 | * // register a library using the PEAR naming convention 39 | * $loader->registerPrefixes(array( 40 | * 'Swift_' => __DIR__.'/Swift', 41 | * )); 42 | * 43 | * // activate the autoloader 44 | * $loader->register(); 45 | * 46 | * In this example, if you try to use a class in the Symfony\Components 47 | * namespace or one of its children (Symfony\Components\Console for instance), 48 | * the autoloader will first look for the class under the components/ 49 | * directory, and it will then fallback to the framework/ directory if not 50 | * found before giving up. 51 | * 52 | * @package Symfony 53 | * @subpackage Framework 54 | * @author Fabien Potencier 55 | */ 56 | class UniversalClassLoader 57 | { 58 | protected $namespaces = array(); 59 | protected $prefixes = array(); 60 | 61 | public function getNamespaces() 62 | { 63 | return $this->namespaces; 64 | } 65 | 66 | public function getPrefixes() 67 | { 68 | return $this->prefixes; 69 | } 70 | 71 | /** 72 | * Registers an array of namespaces 73 | * 74 | * @param array $namespaces An array of namespaces (namespaces as keys and locations as values) 75 | */ 76 | public function registerNamespaces(array $namespaces) 77 | { 78 | $this->namespaces = array_merge($this->namespaces, $namespaces); 79 | } 80 | 81 | /** 82 | * Registers a namespace. 83 | * 84 | * @param string $namespace The namespace 85 | * @param string $path The location of the namespace 86 | */ 87 | public function registerNamespace($namespace, $path) 88 | { 89 | $this->namespaces[$namespace] = $path; 90 | } 91 | 92 | /** 93 | * Registers an array of classes using the PEAR naming convention. 94 | * 95 | * @param array $classes An array of classes (prefixes as keys and locations as values) 96 | */ 97 | public function registerPrefixes(array $classes) 98 | { 99 | $this->prefixes = array_merge($this->prefixes, $classes); 100 | } 101 | 102 | /** 103 | * Registers a set of classes using the PEAR naming convention. 104 | * 105 | * @param string $prefix The classes prefix 106 | * @param string $path The location of the classes 107 | */ 108 | public function registerPrefix($prefix, $path) 109 | { 110 | $this->prefixes[$prefix] = $path; 111 | } 112 | 113 | /** 114 | * Registers this instance as an autoloader. 115 | */ 116 | public function register() 117 | { 118 | spl_autoload_register(array($this, 'loadClass')); 119 | } 120 | 121 | /** 122 | * Loads the given class or interface. 123 | * 124 | * @param string $class The name of the class 125 | */ 126 | public function loadClass($class) 127 | { 128 | if (false !== ($pos = strripos($class, '\\'))) { 129 | // namespaced class name 130 | $namespace = substr($class, 0, $pos); 131 | foreach ($this->namespaces as $ns => $dir) { 132 | if (0 === strpos($namespace, $ns)) { 133 | $class = substr($class, $pos + 1); 134 | $file = $dir.DIRECTORY_SEPARATOR.str_replace('\\', DIRECTORY_SEPARATOR, $namespace).DIRECTORY_SEPARATOR.str_replace('_', DIRECTORY_SEPARATOR, $class).'.php'; 135 | if (file_exists($file)) { 136 | require $file; 137 | } 138 | 139 | return; 140 | } 141 | } 142 | } else { 143 | // PEAR-like class name 144 | foreach ($this->prefixes as $prefix => $dir) { 145 | if (0 === strpos($class, $prefix)) { 146 | $file = $dir.DIRECTORY_SEPARATOR.str_replace('_', DIRECTORY_SEPARATOR, $class).'.php'; 147 | if (file_exists($file)) { 148 | require $file; 149 | } 150 | 151 | return; 152 | } 153 | } 154 | } 155 | } 156 | } 157 | --------------------------------------------------------------------------------