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