1<?php 2 3/* 4 * This file is part of the Assetic package, an OpenSky project. 5 * 6 * (c) 2010-2014 OpenSky Project Inc 7 * 8 * For the full copyright and license information, please view the LICENSE 9 * file that was distributed with this source code. 10 */ 11 12namespace Assetic\Extension\Twig; 13 14use Assetic\Asset\AssetInterface; 15use Assetic\Factory\AssetFactory; 16 17class AsseticTokenParser extends \Twig_TokenParser 18{ 19 private $factory; 20 private $tag; 21 private $output; 22 private $single; 23 private $extensions; 24 25 /** 26 * Constructor. 27 * 28 * Attributes can be added to the tag by passing names as the options 29 * array. These values, if found, will be passed to the factory and node. 30 * 31 * @param AssetFactory $factory The asset factory 32 * @param string $tag The tag name 33 * @param string $output The default output string 34 * @param Boolean $single Whether to force a single asset 35 * @param array $extensions Additional attribute names to look for 36 */ 37 public function __construct(AssetFactory $factory, $tag, $output, $single = false, array $extensions = array()) 38 { 39 $this->factory = $factory; 40 $this->tag = $tag; 41 $this->output = $output; 42 $this->single = $single; 43 $this->extensions = $extensions; 44 } 45 46 public function parse(\Twig_Token $token) 47 { 48 $inputs = array(); 49 $filters = array(); 50 $name = null; 51 $attributes = array( 52 'output' => $this->output, 53 'var_name' => 'asset_url', 54 'vars' => array(), 55 ); 56 57 $stream = $this->parser->getStream(); 58 while (!$stream->test(\Twig_Token::BLOCK_END_TYPE)) { 59 if ($stream->test(\Twig_Token::STRING_TYPE)) { 60 // '@jquery', 'js/src/core/*', 'js/src/extra.js' 61 $inputs[] = $stream->next()->getValue(); 62 } elseif ($stream->test(\Twig_Token::NAME_TYPE, 'filter')) { 63 // filter='yui_js' 64 $stream->next(); 65 $stream->expect(\Twig_Token::OPERATOR_TYPE, '='); 66 $filters = array_merge($filters, array_filter(array_map('trim', explode(',', $stream->expect(\Twig_Token::STRING_TYPE)->getValue())))); 67 } elseif ($stream->test(\Twig_Token::NAME_TYPE, 'output')) { 68 // output='js/packed/*.js' OR output='js/core.js' 69 $stream->next(); 70 $stream->expect(\Twig_Token::OPERATOR_TYPE, '='); 71 $attributes['output'] = $stream->expect(\Twig_Token::STRING_TYPE)->getValue(); 72 } elseif ($stream->test(\Twig_Token::NAME_TYPE, 'name')) { 73 // name='core_js' 74 $stream->next(); 75 $stream->expect(\Twig_Token::OPERATOR_TYPE, '='); 76 $name = $stream->expect(\Twig_Token::STRING_TYPE)->getValue(); 77 } elseif ($stream->test(\Twig_Token::NAME_TYPE, 'as')) { 78 // as='the_url' 79 $stream->next(); 80 $stream->expect(\Twig_Token::OPERATOR_TYPE, '='); 81 $attributes['var_name'] = $stream->expect(\Twig_Token::STRING_TYPE)->getValue(); 82 } elseif ($stream->test(\Twig_Token::NAME_TYPE, 'debug')) { 83 // debug=true 84 $stream->next(); 85 $stream->expect(\Twig_Token::OPERATOR_TYPE, '='); 86 $attributes['debug'] = 'true' == $stream->expect(\Twig_Token::NAME_TYPE, array('true', 'false'))->getValue(); 87 } elseif ($stream->test(\Twig_Token::NAME_TYPE, 'combine')) { 88 // combine=true 89 $stream->next(); 90 $stream->expect(\Twig_Token::OPERATOR_TYPE, '='); 91 $attributes['combine'] = 'true' == $stream->expect(\Twig_Token::NAME_TYPE, array('true', 'false'))->getValue(); 92 } elseif ($stream->test(\Twig_Token::NAME_TYPE, 'vars')) { 93 // vars=['locale','browser'] 94 $stream->next(); 95 $stream->expect(\Twig_Token::OPERATOR_TYPE, '='); 96 $stream->expect(\Twig_Token::PUNCTUATION_TYPE, '['); 97 98 while ($stream->test(\Twig_Token::STRING_TYPE)) { 99 $attributes['vars'][] = $stream->expect(\Twig_Token::STRING_TYPE)->getValue(); 100 101 if (!$stream->test(\Twig_Token::PUNCTUATION_TYPE, ',')) { 102 break; 103 } 104 105 $stream->next(); 106 } 107 108 $stream->expect(\Twig_Token::PUNCTUATION_TYPE, ']'); 109 } elseif ($stream->test(\Twig_Token::NAME_TYPE, $this->extensions)) { 110 // an arbitrary configured attribute 111 $key = $stream->next()->getValue(); 112 $stream->expect(\Twig_Token::OPERATOR_TYPE, '='); 113 $attributes[$key] = $stream->expect(\Twig_Token::STRING_TYPE)->getValue(); 114 } else { 115 $token = $stream->getCurrent(); 116 throw new \Twig_Error_Syntax(sprintf('Unexpected token "%s" of value "%s"', \Twig_Token::typeToEnglish($token->getType()), $token->getValue()), $token->getLine(), $stream->getFilename()); 117 } 118 } 119 120 $stream->expect(\Twig_Token::BLOCK_END_TYPE); 121 122 $body = $this->parser->subparse(array($this, 'testEndTag'), true); 123 124 $stream->expect(\Twig_Token::BLOCK_END_TYPE); 125 126 if ($this->single && 1 < count($inputs)) { 127 $inputs = array_slice($inputs, -1); 128 } 129 130 if (!$name) { 131 $name = $this->factory->generateAssetName($inputs, $filters, $attributes); 132 } 133 134 $asset = $this->factory->createAsset($inputs, $filters, $attributes + array('name' => $name)); 135 136 return $this->createBodyNode($asset, $body, $inputs, $filters, $name, $attributes, $token->getLine(), $this->getTag()); 137 } 138 139 public function getTag() 140 { 141 return $this->tag; 142 } 143 144 public function testEndTag(\Twig_Token $token) 145 { 146 return $token->test(array('end'.$this->getTag())); 147 } 148 149 /** 150 * @param AssetInterface $asset 151 * @param \Twig_Node $body 152 * @param array $inputs 153 * @param array $filters 154 * @param string $name 155 * @param array $attributes 156 * @param int $lineno 157 * @param string $tag 158 * 159 * @return \Twig_Node 160 */ 161 protected function createBodyNode(AssetInterface $asset, \Twig_Node $body, array $inputs, array $filters, $name, array $attributes = array(), $lineno = 0, $tag = null) 162 { 163 $reflector = new \ReflectionMethod($this, 'createNode'); 164 165 if (__CLASS__ !== $reflector->getDeclaringClass()->name) { 166 @trigger_error(sprintf('Overwriting %s::createNode is deprecated since 1.3. Overwrite %s instead.', __CLASS__, __METHOD__), E_USER_DEPRECATED); 167 168 return $this->createNode($asset, $body, $inputs, $filters, $name, $attributes, $lineno, $tag); 169 } 170 171 return new AsseticNode($asset, $body, $inputs, $filters, $name, $attributes, $lineno, $tag); 172 } 173 174 /** 175 * @param AssetInterface $asset 176 * @param \Twig_NodeInterface $body 177 * @param array $inputs 178 * @param array $filters 179 * @param string $name 180 * @param array $attributes 181 * @param int $lineno 182 * @param string $tag 183 * 184 * @return \Twig_Node 185 * 186 * @deprecated since 1.3.0, to be removed in 2.0. Use createBodyNode instead. 187 */ 188 protected function createNode(AssetInterface $asset, \Twig_NodeInterface $body, array $inputs, array $filters, $name, array $attributes = array(), $lineno = 0, $tag = null) 189 { 190 @trigger_error(sprintf('The %s method is deprecated since 1.3 and will be removed in 2.0. Use createBodyNode instead.', __METHOD__), E_USER_DEPRECATED); 191 192 if (!$body instanceof \Twig_Node) { 193 throw new \InvalidArgumentException('The body must be a Twig_Node. Custom implementations of Twig_NodeInterface are not supported.'); 194 } 195 196 return new AsseticNode($asset, $body, $inputs, $filters, $name, $attributes, $lineno, $tag); 197 } 198} 199