@@ -7,62 +7,62 @@ |
||
7 | 7 | */ |
8 | 8 | abstract class IndentScanner extends InputHandler |
9 | 9 | { |
10 | - /** |
|
11 | - * Scan EOS from input & return it if found. |
|
12 | - * |
|
13 | - * @return object|null |
|
14 | - */ |
|
15 | - protected function scanEOS() |
|
16 | - { |
|
17 | - if (!$this->length()) { |
|
18 | - if (count($this->indentStack)) { |
|
19 | - array_shift($this->indentStack); |
|
10 | + /** |
|
11 | + * Scan EOS from input & return it if found. |
|
12 | + * |
|
13 | + * @return object|null |
|
14 | + */ |
|
15 | + protected function scanEOS() |
|
16 | + { |
|
17 | + if (!$this->length()) { |
|
18 | + if (count($this->indentStack)) { |
|
19 | + array_shift($this->indentStack); |
|
20 | 20 | |
21 | - return $this->token('outdent'); |
|
22 | - } |
|
21 | + return $this->token('outdent'); |
|
22 | + } |
|
23 | 23 | |
24 | - return $this->token('eos'); |
|
25 | - } |
|
26 | - } |
|
24 | + return $this->token('eos'); |
|
25 | + } |
|
26 | + } |
|
27 | 27 | |
28 | - /** |
|
29 | - * @return object |
|
30 | - */ |
|
31 | - protected function scanBlank() |
|
32 | - { |
|
33 | - if (preg_match('/^\n *\n/', $this->input, $matches)) { |
|
34 | - $this->consume(substr($matches[0], 0, -1)); // do not cosume the last \r |
|
35 | - $this->lineno++; |
|
28 | + /** |
|
29 | + * @return object |
|
30 | + */ |
|
31 | + protected function scanBlank() |
|
32 | + { |
|
33 | + if (preg_match('/^\n *\n/', $this->input, $matches)) { |
|
34 | + $this->consume(substr($matches[0], 0, -1)); // do not cosume the last \r |
|
35 | + $this->lineno++; |
|
36 | 36 | |
37 | - if ($this->pipeless) { |
|
38 | - return $this->token('text', ''); |
|
39 | - } |
|
37 | + if ($this->pipeless) { |
|
38 | + return $this->token('text', ''); |
|
39 | + } |
|
40 | 40 | |
41 | - return $this->next(); |
|
42 | - } |
|
43 | - } |
|
41 | + return $this->next(); |
|
42 | + } |
|
43 | + } |
|
44 | 44 | |
45 | - /** |
|
46 | - * @throws \ErrorException |
|
47 | - * |
|
48 | - * @return mixed|object |
|
49 | - */ |
|
50 | - protected function scanIndent() |
|
51 | - { |
|
52 | - $matches = $this->getNextIndent(); |
|
45 | + /** |
|
46 | + * @throws \ErrorException |
|
47 | + * |
|
48 | + * @return mixed|object |
|
49 | + */ |
|
50 | + protected function scanIndent() |
|
51 | + { |
|
52 | + $matches = $this->getNextIndent(); |
|
53 | 53 | |
54 | - if ($matches !== null) { |
|
55 | - $indents = strlen($matches[1]); |
|
54 | + if ($matches !== null) { |
|
55 | + $indents = strlen($matches[1]); |
|
56 | 56 | |
57 | - $this->lineno++; |
|
58 | - $this->consume($matches[0]); |
|
59 | - $firstChar = substr($this->input, 0, 1); |
|
57 | + $this->lineno++; |
|
58 | + $this->consume($matches[0]); |
|
59 | + $firstChar = substr($this->input, 0, 1); |
|
60 | 60 | |
61 | - if ($this->length() && (' ' === $firstChar || "\t" === $firstChar)) { |
|
62 | - throw new \ErrorException('Invalid indentation, you can use tabs or spaces but not both', 20); |
|
63 | - } |
|
61 | + if ($this->length() && (' ' === $firstChar || "\t" === $firstChar)) { |
|
62 | + throw new \ErrorException('Invalid indentation, you can use tabs or spaces but not both', 20); |
|
63 | + } |
|
64 | 64 | |
65 | - return $this->getTokenFromIndent($firstChar, $indents); |
|
66 | - } |
|
67 | - } |
|
65 | + return $this->getTokenFromIndent($firstChar, $indents); |
|
66 | + } |
|
67 | + } |
|
68 | 68 | } |
@@ -7,27 +7,27 @@ |
||
7 | 7 | */ |
8 | 8 | abstract class CaseScanner extends BlockScanner |
9 | 9 | { |
10 | - /** |
|
11 | - * @return object |
|
12 | - */ |
|
13 | - protected function scanCase() |
|
14 | - { |
|
15 | - return $this->scan('/^case +([^\n]+)/', 'case'); |
|
16 | - } |
|
10 | + /** |
|
11 | + * @return object |
|
12 | + */ |
|
13 | + protected function scanCase() |
|
14 | + { |
|
15 | + return $this->scan('/^case +([^\n]+)/', 'case'); |
|
16 | + } |
|
17 | 17 | |
18 | - /** |
|
19 | - * @return object |
|
20 | - */ |
|
21 | - protected function scanWhen() |
|
22 | - { |
|
23 | - return $this->scan('/^when +((::|[^\n:]+)+)/', 'when'); |
|
24 | - } |
|
18 | + /** |
|
19 | + * @return object |
|
20 | + */ |
|
21 | + protected function scanWhen() |
|
22 | + { |
|
23 | + return $this->scan('/^when +((::|[^\n:]+)+)/', 'when'); |
|
24 | + } |
|
25 | 25 | |
26 | - /** |
|
27 | - * @return object |
|
28 | - */ |
|
29 | - protected function scanDefault() |
|
30 | - { |
|
31 | - return $this->scan('/^default */', 'default'); |
|
32 | - } |
|
26 | + /** |
|
27 | + * @return object |
|
28 | + */ |
|
29 | + protected function scanDefault() |
|
30 | + { |
|
31 | + return $this->scan('/^default */', 'default'); |
|
32 | + } |
|
33 | 33 | } |
@@ -7,36 +7,36 @@ |
||
7 | 7 | */ |
8 | 8 | class StringAttribute |
9 | 9 | { |
10 | - protected $char; |
|
10 | + protected $char; |
|
11 | 11 | |
12 | - public function __construct($char) |
|
13 | - { |
|
14 | - $this->char = $char; |
|
15 | - } |
|
12 | + public function __construct($char) |
|
13 | + { |
|
14 | + $this->char = $char; |
|
15 | + } |
|
16 | 16 | |
17 | - public function parse(AttributesState $states, &$val, &$quote) |
|
18 | - { |
|
19 | - switch ($states->current()) { |
|
20 | - case 'key': |
|
21 | - $states->push('key char'); |
|
22 | - break; |
|
17 | + public function parse(AttributesState $states, &$val, &$quote) |
|
18 | + { |
|
19 | + switch ($states->current()) { |
|
20 | + case 'key': |
|
21 | + $states->push('key char'); |
|
22 | + break; |
|
23 | 23 | |
24 | - case 'key char': |
|
25 | - $states->pop(); |
|
26 | - break; |
|
24 | + case 'key char': |
|
25 | + $states->pop(); |
|
26 | + break; |
|
27 | 27 | |
28 | - case 'string': |
|
29 | - if ($this->char === $quote) { |
|
30 | - $states->pop(); |
|
31 | - } |
|
32 | - $val .= $this->char; |
|
33 | - break; |
|
28 | + case 'string': |
|
29 | + if ($this->char === $quote) { |
|
30 | + $states->pop(); |
|
31 | + } |
|
32 | + $val .= $this->char; |
|
33 | + break; |
|
34 | 34 | |
35 | - default: |
|
36 | - $states->push('string'); |
|
37 | - $val .= $this->char; |
|
38 | - $quote = $this->char; |
|
39 | - break; |
|
40 | - } |
|
41 | - } |
|
35 | + default: |
|
36 | + $states->push('string'); |
|
37 | + $val .= $this->char; |
|
38 | + $quote = $this->char; |
|
39 | + break; |
|
40 | + } |
|
41 | + } |
|
42 | 42 | } |
@@ -7,69 +7,69 @@ |
||
7 | 7 | */ |
8 | 8 | abstract class BlockScanner extends IndentScanner |
9 | 9 | { |
10 | - /** |
|
11 | - * @return object |
|
12 | - */ |
|
13 | - protected function scanExtends() |
|
14 | - { |
|
15 | - return $this->scan('/^extends? +([^\n]+)/', 'extends'); |
|
16 | - } |
|
10 | + /** |
|
11 | + * @return object |
|
12 | + */ |
|
13 | + protected function scanExtends() |
|
14 | + { |
|
15 | + return $this->scan('/^extends? +([^\n]+)/', 'extends'); |
|
16 | + } |
|
17 | 17 | |
18 | - /** |
|
19 | - * @return object |
|
20 | - */ |
|
21 | - protected function scanPrepend() |
|
22 | - { |
|
23 | - if (preg_match('/^prepend +([^\n]+)/', $this->input, $matches)) { |
|
24 | - $this->consume($matches[0]); |
|
25 | - $token = $this->token('block', $matches[1]); |
|
26 | - $token->mode = 'prepend'; |
|
18 | + /** |
|
19 | + * @return object |
|
20 | + */ |
|
21 | + protected function scanPrepend() |
|
22 | + { |
|
23 | + if (preg_match('/^prepend +([^\n]+)/', $this->input, $matches)) { |
|
24 | + $this->consume($matches[0]); |
|
25 | + $token = $this->token('block', $matches[1]); |
|
26 | + $token->mode = 'prepend'; |
|
27 | 27 | |
28 | - return $token; |
|
29 | - } |
|
30 | - } |
|
28 | + return $token; |
|
29 | + } |
|
30 | + } |
|
31 | 31 | |
32 | - /** |
|
33 | - * @return object |
|
34 | - */ |
|
35 | - protected function scanAppend() |
|
36 | - { |
|
37 | - if (preg_match('/^append +([^\n]+)/', $this->input, $matches)) { |
|
38 | - $this->consume($matches[0]); |
|
39 | - $token = $this->token('block', $matches[1]); |
|
40 | - $token->mode = 'append'; |
|
32 | + /** |
|
33 | + * @return object |
|
34 | + */ |
|
35 | + protected function scanAppend() |
|
36 | + { |
|
37 | + if (preg_match('/^append +([^\n]+)/', $this->input, $matches)) { |
|
38 | + $this->consume($matches[0]); |
|
39 | + $token = $this->token('block', $matches[1]); |
|
40 | + $token->mode = 'append'; |
|
41 | 41 | |
42 | - return $token; |
|
43 | - } |
|
44 | - } |
|
42 | + return $token; |
|
43 | + } |
|
44 | + } |
|
45 | 45 | |
46 | - /** |
|
47 | - * @return object |
|
48 | - */ |
|
49 | - protected function scanBlock() |
|
50 | - { |
|
51 | - if (preg_match("/^block\b *(?:(prepend|append) +)?([^\n]*)/", $this->input, $matches)) { |
|
52 | - $this->consume($matches[0]); |
|
53 | - $token = $this->token('block', $matches[2]); |
|
54 | - $token->mode = strlen($matches[1]) === 0 ? 'replace' : $matches[1]; |
|
46 | + /** |
|
47 | + * @return object |
|
48 | + */ |
|
49 | + protected function scanBlock() |
|
50 | + { |
|
51 | + if (preg_match("/^block\b *(?:(prepend|append) +)?([^\n]*)/", $this->input, $matches)) { |
|
52 | + $this->consume($matches[0]); |
|
53 | + $token = $this->token('block', $matches[2]); |
|
54 | + $token->mode = strlen($matches[1]) === 0 ? 'replace' : $matches[1]; |
|
55 | 55 | |
56 | - return $token; |
|
57 | - } |
|
58 | - } |
|
56 | + return $token; |
|
57 | + } |
|
58 | + } |
|
59 | 59 | |
60 | - /** |
|
61 | - * @return object |
|
62 | - */ |
|
63 | - protected function scanYield() |
|
64 | - { |
|
65 | - return $this->scan('/^yield */', 'yield'); |
|
66 | - } |
|
60 | + /** |
|
61 | + * @return object |
|
62 | + */ |
|
63 | + protected function scanYield() |
|
64 | + { |
|
65 | + return $this->scan('/^yield */', 'yield'); |
|
66 | + } |
|
67 | 67 | |
68 | - /** |
|
69 | - * @return object |
|
70 | - */ |
|
71 | - protected function scanInclude() |
|
72 | - { |
|
73 | - return $this->scan('/^include +([^\n]+)/', 'include'); |
|
74 | - } |
|
68 | + /** |
|
69 | + * @return object |
|
70 | + */ |
|
71 | + protected function scanInclude() |
|
72 | + { |
|
73 | + return $this->scan('/^include +([^\n]+)/', 'include'); |
|
74 | + } |
|
75 | 75 | } |
@@ -9,191 +9,191 @@ |
||
9 | 9 | */ |
10 | 10 | class Attributes |
11 | 11 | { |
12 | - protected $token; |
|
13 | - |
|
14 | - public function __construct($token = null) |
|
15 | - { |
|
16 | - $this->token = $token; |
|
17 | - } |
|
18 | - |
|
19 | - protected function parseSpace($states, $escapedAttribute, &$key, &$val, $char, $previousNonBlankChar, $nextChar) |
|
20 | - { |
|
21 | - if ( |
|
22 | - in_array($states->current(), array('expr', 'array', 'string', 'object')) || |
|
23 | - ( |
|
24 | - ($char === ' ' || $char === "\t") && |
|
25 | - ( |
|
26 | - !preg_match('/^[a-zA-Z0-9_\\x7f-\\xff"\'\\]\\)\\}]$/', $previousNonBlankChar) || |
|
27 | - !preg_match('/^[a-zA-Z0-9_]$/', $nextChar) |
|
28 | - ) |
|
29 | - ) |
|
30 | - ) { |
|
31 | - $val .= $char; |
|
32 | - |
|
33 | - return; |
|
34 | - } |
|
35 | - |
|
36 | - $states->push('key'); |
|
37 | - $val = trim($val); |
|
38 | - $key = trim($key); |
|
39 | - |
|
40 | - if (empty($key)) { |
|
41 | - return; |
|
42 | - } |
|
43 | - |
|
44 | - $key = preg_replace( |
|
45 | - array('/^[\'\"]|[\'\"]$/', '/\!/'), '', $key |
|
46 | - ); |
|
47 | - $this->token->escaped[$key] = $escapedAttribute; |
|
48 | - |
|
49 | - $this->token->attributes[$key] = ('' === $val) ? true : $this->interpolate($val); |
|
50 | - |
|
51 | - $key = ''; |
|
52 | - $val = ''; |
|
53 | - } |
|
54 | - |
|
55 | - protected function replaceInterpolationsInStrings($match) |
|
56 | - { |
|
57 | - $quote = $match[1]; |
|
58 | - |
|
59 | - return str_replace('\\#{', '#{', preg_replace_callback('/(?<!\\\\)#{([^}]+)}/', function ($match) use ($quote) { |
|
60 | - return $quote . ' . ' . CommonUtils::addDollarIfNeeded(preg_replace_callback( |
|
61 | - '/(?<![a-zA-Z0-9_\$])(\$?[a-zA-Z_][a-zA-Z0-9_]*)\.([a-zA-Z_][a-zA-Z0-9_]*)(?![a-zA-Z0-9_])/', |
|
62 | - function ($match) { |
|
63 | - return CommonUtils::getGetter($match[1], $match[2]); |
|
64 | - }, |
|
65 | - $match[1] |
|
66 | - )) . ' . ' . $quote; |
|
67 | - }, $match[0])); |
|
68 | - } |
|
69 | - |
|
70 | - protected function interpolate($attr) |
|
71 | - { |
|
72 | - return preg_replace_callback('/([\'"]).*?(?<!\\\\)(?:\\\\\\\\)*\1/', array($this, 'replaceInterpolationsInStrings'), $attr); |
|
73 | - } |
|
74 | - |
|
75 | - protected function parseEqual($states, &$escapedAttribute, &$key, &$val, $char, $previousChar) |
|
76 | - { |
|
77 | - switch ($states->current()) { |
|
78 | - case 'key char': |
|
79 | - $key .= $char; |
|
80 | - break; |
|
81 | - |
|
82 | - case 'val': |
|
83 | - case 'expr': |
|
84 | - case 'array': |
|
85 | - case 'string': |
|
86 | - case 'object': |
|
87 | - $val .= $char; |
|
88 | - break; |
|
89 | - |
|
90 | - default: |
|
91 | - $escapedAttribute = '!' !== $previousChar; |
|
92 | - $states->push('val'); |
|
93 | - } |
|
94 | - } |
|
95 | - |
|
96 | - protected function parsePairs($states, $char, &$val) |
|
97 | - { |
|
98 | - switch ($char) { |
|
99 | - case '(': |
|
100 | - $states->pushFor('expr', 'val', 'expr'); |
|
101 | - break; |
|
102 | - |
|
103 | - case ')': |
|
104 | - $states->popFor('val', 'expr'); |
|
105 | - break; |
|
106 | - |
|
107 | - case '{': |
|
108 | - $states->pushFor('object', 'val'); |
|
109 | - break; |
|
110 | - |
|
111 | - case '}': |
|
112 | - $states->popFor('object'); |
|
113 | - break; |
|
114 | - |
|
115 | - case '[': |
|
116 | - $states->pushFor('array', 'val'); |
|
117 | - break; |
|
118 | - |
|
119 | - case ']': |
|
120 | - $states->popFor('array'); |
|
121 | - break; |
|
122 | - |
|
123 | - default: |
|
124 | - return false; |
|
125 | - } |
|
126 | - $val .= $char; |
|
127 | - |
|
128 | - return true; |
|
129 | - } |
|
130 | - |
|
131 | - protected function parseString(&$states, &$key, &$val, &$quote, $char) |
|
132 | - { |
|
133 | - if (($char === '"' || $char === "'") && !CommonUtils::escapedEnd($val)) { |
|
134 | - $stringParser = new StringAttribute($char); |
|
135 | - $stringParser->parse($states, $val, $quote); |
|
136 | - |
|
137 | - return; |
|
138 | - } |
|
139 | - ${in_array($states->current(), array('key', 'key char')) ? 'key' : 'val'} .= $char; |
|
140 | - } |
|
141 | - |
|
142 | - public function parseChar($char, &$nextChar, &$key, &$val, &$quote, $states, &$escapedAttribute, &$previousChar, &$previousNonBlankChar) |
|
143 | - { |
|
144 | - if ($this->parsePairs($states, $char, $val)) { |
|
145 | - return; |
|
146 | - } |
|
147 | - |
|
148 | - switch ($char) { |
|
149 | - case ',': |
|
150 | - case "\n": |
|
151 | - case "\t": |
|
152 | - case ' ': |
|
153 | - $this->parseSpace($states, $escapedAttribute, $key, $val, $char, $previousNonBlankChar, $nextChar); |
|
154 | - break; |
|
155 | - |
|
156 | - case '=': |
|
157 | - $this->parseEqual($states, $escapedAttribute, $key, $val, $char, $previousChar); |
|
158 | - break; |
|
159 | - |
|
160 | - default: |
|
161 | - $this->parseString($states, $key, $val, $quote, $char); |
|
162 | - } |
|
163 | - } |
|
164 | - |
|
165 | - protected function getParseFunction(&$key, &$val, &$quote, $states, &$escapedAttribute, &$previousChar, &$previousNonBlankChar, $parser) |
|
166 | - { |
|
167 | - return function ($char, $nextChar = '') use (&$key, &$val, &$quote, $states, &$escapedAttribute, &$previousChar, &$previousNonBlankChar, $parser) { |
|
168 | - $parser->parseChar($char, $nextChar, $key, $val, $quote, $states, $escapedAttribute, $previousChar, $previousNonBlankChar); |
|
169 | - $previousChar = $char; |
|
170 | - if (trim($char) !== '') { |
|
171 | - $previousNonBlankChar = $char; |
|
172 | - } |
|
173 | - }; |
|
174 | - } |
|
175 | - |
|
176 | - /** |
|
177 | - * @return object |
|
178 | - */ |
|
179 | - public function parseWith($str) |
|
180 | - { |
|
181 | - $parser = $this; |
|
182 | - |
|
183 | - $key = ''; |
|
184 | - $val = ''; |
|
185 | - $quote = ''; |
|
186 | - $states = new AttributesState(); |
|
187 | - $escapedAttribute = ''; |
|
188 | - $previousChar = ''; |
|
189 | - $previousNonBlankChar = ''; |
|
190 | - |
|
191 | - $parse = $this->getParseFunction($key, $val, $quote, $states, $escapedAttribute, $previousChar, $previousNonBlankChar, $parser); |
|
192 | - |
|
193 | - for ($i = 0; $i < strlen($str); $i++) { |
|
194 | - $parse(substr($str, $i, 1), substr($str, $i + 1, 1)); |
|
195 | - } |
|
196 | - |
|
197 | - $parse(','); |
|
198 | - } |
|
12 | + protected $token; |
|
13 | + |
|
14 | + public function __construct($token = null) |
|
15 | + { |
|
16 | + $this->token = $token; |
|
17 | + } |
|
18 | + |
|
19 | + protected function parseSpace($states, $escapedAttribute, &$key, &$val, $char, $previousNonBlankChar, $nextChar) |
|
20 | + { |
|
21 | + if ( |
|
22 | + in_array($states->current(), array('expr', 'array', 'string', 'object')) || |
|
23 | + ( |
|
24 | + ($char === ' ' || $char === "\t") && |
|
25 | + ( |
|
26 | + !preg_match('/^[a-zA-Z0-9_\\x7f-\\xff"\'\\]\\)\\}]$/', $previousNonBlankChar) || |
|
27 | + !preg_match('/^[a-zA-Z0-9_]$/', $nextChar) |
|
28 | + ) |
|
29 | + ) |
|
30 | + ) { |
|
31 | + $val .= $char; |
|
32 | + |
|
33 | + return; |
|
34 | + } |
|
35 | + |
|
36 | + $states->push('key'); |
|
37 | + $val = trim($val); |
|
38 | + $key = trim($key); |
|
39 | + |
|
40 | + if (empty($key)) { |
|
41 | + return; |
|
42 | + } |
|
43 | + |
|
44 | + $key = preg_replace( |
|
45 | + array('/^[\'\"]|[\'\"]$/', '/\!/'), '', $key |
|
46 | + ); |
|
47 | + $this->token->escaped[$key] = $escapedAttribute; |
|
48 | + |
|
49 | + $this->token->attributes[$key] = ('' === $val) ? true : $this->interpolate($val); |
|
50 | + |
|
51 | + $key = ''; |
|
52 | + $val = ''; |
|
53 | + } |
|
54 | + |
|
55 | + protected function replaceInterpolationsInStrings($match) |
|
56 | + { |
|
57 | + $quote = $match[1]; |
|
58 | + |
|
59 | + return str_replace('\\#{', '#{', preg_replace_callback('/(?<!\\\\)#{([^}]+)}/', function ($match) use ($quote) { |
|
60 | + return $quote . ' . ' . CommonUtils::addDollarIfNeeded(preg_replace_callback( |
|
61 | + '/(?<![a-zA-Z0-9_\$])(\$?[a-zA-Z_][a-zA-Z0-9_]*)\.([a-zA-Z_][a-zA-Z0-9_]*)(?![a-zA-Z0-9_])/', |
|
62 | + function ($match) { |
|
63 | + return CommonUtils::getGetter($match[1], $match[2]); |
|
64 | + }, |
|
65 | + $match[1] |
|
66 | + )) . ' . ' . $quote; |
|
67 | + }, $match[0])); |
|
68 | + } |
|
69 | + |
|
70 | + protected function interpolate($attr) |
|
71 | + { |
|
72 | + return preg_replace_callback('/([\'"]).*?(?<!\\\\)(?:\\\\\\\\)*\1/', array($this, 'replaceInterpolationsInStrings'), $attr); |
|
73 | + } |
|
74 | + |
|
75 | + protected function parseEqual($states, &$escapedAttribute, &$key, &$val, $char, $previousChar) |
|
76 | + { |
|
77 | + switch ($states->current()) { |
|
78 | + case 'key char': |
|
79 | + $key .= $char; |
|
80 | + break; |
|
81 | + |
|
82 | + case 'val': |
|
83 | + case 'expr': |
|
84 | + case 'array': |
|
85 | + case 'string': |
|
86 | + case 'object': |
|
87 | + $val .= $char; |
|
88 | + break; |
|
89 | + |
|
90 | + default: |
|
91 | + $escapedAttribute = '!' !== $previousChar; |
|
92 | + $states->push('val'); |
|
93 | + } |
|
94 | + } |
|
95 | + |
|
96 | + protected function parsePairs($states, $char, &$val) |
|
97 | + { |
|
98 | + switch ($char) { |
|
99 | + case '(': |
|
100 | + $states->pushFor('expr', 'val', 'expr'); |
|
101 | + break; |
|
102 | + |
|
103 | + case ')': |
|
104 | + $states->popFor('val', 'expr'); |
|
105 | + break; |
|
106 | + |
|
107 | + case '{': |
|
108 | + $states->pushFor('object', 'val'); |
|
109 | + break; |
|
110 | + |
|
111 | + case '}': |
|
112 | + $states->popFor('object'); |
|
113 | + break; |
|
114 | + |
|
115 | + case '[': |
|
116 | + $states->pushFor('array', 'val'); |
|
117 | + break; |
|
118 | + |
|
119 | + case ']': |
|
120 | + $states->popFor('array'); |
|
121 | + break; |
|
122 | + |
|
123 | + default: |
|
124 | + return false; |
|
125 | + } |
|
126 | + $val .= $char; |
|
127 | + |
|
128 | + return true; |
|
129 | + } |
|
130 | + |
|
131 | + protected function parseString(&$states, &$key, &$val, &$quote, $char) |
|
132 | + { |
|
133 | + if (($char === '"' || $char === "'") && !CommonUtils::escapedEnd($val)) { |
|
134 | + $stringParser = new StringAttribute($char); |
|
135 | + $stringParser->parse($states, $val, $quote); |
|
136 | + |
|
137 | + return; |
|
138 | + } |
|
139 | + ${in_array($states->current(), array('key', 'key char')) ? 'key' : 'val'} .= $char; |
|
140 | + } |
|
141 | + |
|
142 | + public function parseChar($char, &$nextChar, &$key, &$val, &$quote, $states, &$escapedAttribute, &$previousChar, &$previousNonBlankChar) |
|
143 | + { |
|
144 | + if ($this->parsePairs($states, $char, $val)) { |
|
145 | + return; |
|
146 | + } |
|
147 | + |
|
148 | + switch ($char) { |
|
149 | + case ',': |
|
150 | + case "\n": |
|
151 | + case "\t": |
|
152 | + case ' ': |
|
153 | + $this->parseSpace($states, $escapedAttribute, $key, $val, $char, $previousNonBlankChar, $nextChar); |
|
154 | + break; |
|
155 | + |
|
156 | + case '=': |
|
157 | + $this->parseEqual($states, $escapedAttribute, $key, $val, $char, $previousChar); |
|
158 | + break; |
|
159 | + |
|
160 | + default: |
|
161 | + $this->parseString($states, $key, $val, $quote, $char); |
|
162 | + } |
|
163 | + } |
|
164 | + |
|
165 | + protected function getParseFunction(&$key, &$val, &$quote, $states, &$escapedAttribute, &$previousChar, &$previousNonBlankChar, $parser) |
|
166 | + { |
|
167 | + return function ($char, $nextChar = '') use (&$key, &$val, &$quote, $states, &$escapedAttribute, &$previousChar, &$previousNonBlankChar, $parser) { |
|
168 | + $parser->parseChar($char, $nextChar, $key, $val, $quote, $states, $escapedAttribute, $previousChar, $previousNonBlankChar); |
|
169 | + $previousChar = $char; |
|
170 | + if (trim($char) !== '') { |
|
171 | + $previousNonBlankChar = $char; |
|
172 | + } |
|
173 | + }; |
|
174 | + } |
|
175 | + |
|
176 | + /** |
|
177 | + * @return object |
|
178 | + */ |
|
179 | + public function parseWith($str) |
|
180 | + { |
|
181 | + $parser = $this; |
|
182 | + |
|
183 | + $key = ''; |
|
184 | + $val = ''; |
|
185 | + $quote = ''; |
|
186 | + $states = new AttributesState(); |
|
187 | + $escapedAttribute = ''; |
|
188 | + $previousChar = ''; |
|
189 | + $previousNonBlankChar = ''; |
|
190 | + |
|
191 | + $parse = $this->getParseFunction($key, $val, $quote, $states, $escapedAttribute, $previousChar, $previousNonBlankChar, $parser); |
|
192 | + |
|
193 | + for ($i = 0; $i < strlen($str); $i++) { |
|
194 | + $parse(substr($str, $i, 1), substr($str, $i + 1, 1)); |
|
195 | + } |
|
196 | + |
|
197 | + $parse(','); |
|
198 | + } |
|
199 | 199 | } |
@@ -7,39 +7,39 @@ |
||
7 | 7 | */ |
8 | 8 | class AttributesState |
9 | 9 | { |
10 | - protected $states = array('key'); |
|
11 | - |
|
12 | - public function current() |
|
13 | - { |
|
14 | - return $this->states[count($this->states) - 1]; |
|
15 | - } |
|
16 | - |
|
17 | - protected function isIn() |
|
18 | - { |
|
19 | - return in_array($this->current(), func_get_args()); |
|
20 | - } |
|
21 | - |
|
22 | - public function pop() |
|
23 | - { |
|
24 | - array_pop($this->states); |
|
25 | - } |
|
26 | - |
|
27 | - public function push($value) |
|
28 | - { |
|
29 | - array_push($this->states, $value); |
|
30 | - } |
|
31 | - |
|
32 | - public function popFor() |
|
33 | - { |
|
34 | - if (call_user_func_array(array($this, 'isIn'), func_get_args())) { |
|
35 | - $this->pop(); |
|
36 | - } |
|
37 | - } |
|
38 | - |
|
39 | - public function pushFor($value) |
|
40 | - { |
|
41 | - if (call_user_func_array(array($this, 'isIn'), array_slice(func_get_args(), 1))) { |
|
42 | - $this->push($value); |
|
43 | - } |
|
44 | - } |
|
10 | + protected $states = array('key'); |
|
11 | + |
|
12 | + public function current() |
|
13 | + { |
|
14 | + return $this->states[count($this->states) - 1]; |
|
15 | + } |
|
16 | + |
|
17 | + protected function isIn() |
|
18 | + { |
|
19 | + return in_array($this->current(), func_get_args()); |
|
20 | + } |
|
21 | + |
|
22 | + public function pop() |
|
23 | + { |
|
24 | + array_pop($this->states); |
|
25 | + } |
|
26 | + |
|
27 | + public function push($value) |
|
28 | + { |
|
29 | + array_push($this->states, $value); |
|
30 | + } |
|
31 | + |
|
32 | + public function popFor() |
|
33 | + { |
|
34 | + if (call_user_func_array(array($this, 'isIn'), func_get_args())) { |
|
35 | + $this->pop(); |
|
36 | + } |
|
37 | + } |
|
38 | + |
|
39 | + public function pushFor($value) |
|
40 | + { |
|
41 | + if (call_user_func_array(array($this, 'isIn'), array_slice(func_get_args(), 1))) { |
|
42 | + $this->push($value); |
|
43 | + } |
|
44 | + } |
|
45 | 45 | } |
@@ -7,126 +7,126 @@ |
||
7 | 7 | */ |
8 | 8 | abstract class InputHandler |
9 | 9 | { |
10 | - /** |
|
11 | - * @var string |
|
12 | - */ |
|
13 | - public $input; |
|
14 | - |
|
15 | - /** |
|
16 | - * @var array |
|
17 | - */ |
|
18 | - protected $deferred = array(); |
|
19 | - |
|
20 | - /** |
|
21 | - * @var array |
|
22 | - */ |
|
23 | - protected $indentStack = array(); |
|
24 | - |
|
25 | - /** |
|
26 | - * @var array |
|
27 | - */ |
|
28 | - protected $stash = array(); |
|
29 | - |
|
30 | - /** |
|
31 | - * Set lexer input. |
|
32 | - * |
|
33 | - * @param string $input input string |
|
34 | - */ |
|
35 | - public function setInput($input) |
|
36 | - { |
|
37 | - $this->input = preg_replace("/\r\n|\r/", "\n", $input); |
|
38 | - $this->lineno = 1; |
|
39 | - $this->deferred = array(); |
|
40 | - $this->indentStack = array(); |
|
41 | - $this->stash = array(); |
|
42 | - } |
|
43 | - |
|
44 | - /** |
|
45 | - * @return int |
|
46 | - */ |
|
47 | - public function length() |
|
48 | - { |
|
49 | - return strlen($this->input); |
|
50 | - } |
|
51 | - |
|
52 | - /** |
|
53 | - * @param $code |
|
54 | - * |
|
55 | - * @return string |
|
56 | - */ |
|
57 | - protected function normalizeCode($code) |
|
58 | - { |
|
59 | - return $code = (substr($code, -1) === ':' && substr($code, -2, 1) !== ':') |
|
60 | - ? substr($code, 0, -1) |
|
61 | - : $code; |
|
62 | - } |
|
63 | - |
|
64 | - protected function testIndent($indent) |
|
65 | - { |
|
66 | - if (!preg_match('/^' . $indent . '/', substr($this->input, 1), $matches)) { |
|
67 | - return; |
|
68 | - } |
|
69 | - |
|
70 | - if (!isset($this->identRE)) { |
|
71 | - $this->identRE = $indent; |
|
72 | - } |
|
73 | - |
|
74 | - return array( |
|
75 | - "\n" . $matches[0], |
|
76 | - $matches[0], |
|
77 | - ); |
|
78 | - } |
|
79 | - |
|
80 | - protected function getNextIndent() |
|
81 | - { |
|
82 | - if (substr($this->input, 0, 1) !== "\n") { |
|
83 | - return; |
|
84 | - } |
|
85 | - |
|
86 | - $indents = isset($this->identRE) |
|
87 | - ? array($this->identRE) |
|
88 | - : ($this->allowMixedIndent |
|
89 | - ? array('[\\t ]*') |
|
90 | - : array('\\t+', ' *') |
|
91 | - ); |
|
92 | - |
|
93 | - foreach ($indents as $indent) { |
|
94 | - if ($matches = $this->testIndent($indent)) { |
|
95 | - return $matches; |
|
96 | - } |
|
97 | - } |
|
98 | - } |
|
99 | - |
|
100 | - protected function getWhiteSpacesTokens($indents) |
|
101 | - { |
|
102 | - if ($indents && count($this->indentStack) && $indents === $this->indentStack[0]) { |
|
103 | - return $this->token('newline'); |
|
104 | - } |
|
105 | - |
|
106 | - if ($indents) { |
|
107 | - array_unshift($this->indentStack, $indents); |
|
108 | - |
|
109 | - return $this->token('indent', $indents); |
|
110 | - } |
|
111 | - |
|
112 | - return $this->token('newline'); |
|
113 | - } |
|
114 | - |
|
115 | - protected function getTokenFromIndent($firstChar, $indents) |
|
116 | - { |
|
117 | - if ($this->length() && $firstChar === "\n") { |
|
118 | - return $this->token('newline'); |
|
119 | - } |
|
120 | - |
|
121 | - if (count($this->indentStack) && $indents < $this->indentStack[0]) { |
|
122 | - while (count($this->indentStack) && $indents < $this->indentStack[0]) { |
|
123 | - array_push($this->stash, $this->token('outdent')); |
|
124 | - array_shift($this->indentStack); |
|
125 | - } |
|
126 | - |
|
127 | - return array_pop($this->stash); |
|
128 | - } |
|
129 | - |
|
130 | - return $this->getWhiteSpacesTokens($indents); |
|
131 | - } |
|
10 | + /** |
|
11 | + * @var string |
|
12 | + */ |
|
13 | + public $input; |
|
14 | + |
|
15 | + /** |
|
16 | + * @var array |
|
17 | + */ |
|
18 | + protected $deferred = array(); |
|
19 | + |
|
20 | + /** |
|
21 | + * @var array |
|
22 | + */ |
|
23 | + protected $indentStack = array(); |
|
24 | + |
|
25 | + /** |
|
26 | + * @var array |
|
27 | + */ |
|
28 | + protected $stash = array(); |
|
29 | + |
|
30 | + /** |
|
31 | + * Set lexer input. |
|
32 | + * |
|
33 | + * @param string $input input string |
|
34 | + */ |
|
35 | + public function setInput($input) |
|
36 | + { |
|
37 | + $this->input = preg_replace("/\r\n|\r/", "\n", $input); |
|
38 | + $this->lineno = 1; |
|
39 | + $this->deferred = array(); |
|
40 | + $this->indentStack = array(); |
|
41 | + $this->stash = array(); |
|
42 | + } |
|
43 | + |
|
44 | + /** |
|
45 | + * @return int |
|
46 | + */ |
|
47 | + public function length() |
|
48 | + { |
|
49 | + return strlen($this->input); |
|
50 | + } |
|
51 | + |
|
52 | + /** |
|
53 | + * @param $code |
|
54 | + * |
|
55 | + * @return string |
|
56 | + */ |
|
57 | + protected function normalizeCode($code) |
|
58 | + { |
|
59 | + return $code = (substr($code, -1) === ':' && substr($code, -2, 1) !== ':') |
|
60 | + ? substr($code, 0, -1) |
|
61 | + : $code; |
|
62 | + } |
|
63 | + |
|
64 | + protected function testIndent($indent) |
|
65 | + { |
|
66 | + if (!preg_match('/^' . $indent . '/', substr($this->input, 1), $matches)) { |
|
67 | + return; |
|
68 | + } |
|
69 | + |
|
70 | + if (!isset($this->identRE)) { |
|
71 | + $this->identRE = $indent; |
|
72 | + } |
|
73 | + |
|
74 | + return array( |
|
75 | + "\n" . $matches[0], |
|
76 | + $matches[0], |
|
77 | + ); |
|
78 | + } |
|
79 | + |
|
80 | + protected function getNextIndent() |
|
81 | + { |
|
82 | + if (substr($this->input, 0, 1) !== "\n") { |
|
83 | + return; |
|
84 | + } |
|
85 | + |
|
86 | + $indents = isset($this->identRE) |
|
87 | + ? array($this->identRE) |
|
88 | + : ($this->allowMixedIndent |
|
89 | + ? array('[\\t ]*') |
|
90 | + : array('\\t+', ' *') |
|
91 | + ); |
|
92 | + |
|
93 | + foreach ($indents as $indent) { |
|
94 | + if ($matches = $this->testIndent($indent)) { |
|
95 | + return $matches; |
|
96 | + } |
|
97 | + } |
|
98 | + } |
|
99 | + |
|
100 | + protected function getWhiteSpacesTokens($indents) |
|
101 | + { |
|
102 | + if ($indents && count($this->indentStack) && $indents === $this->indentStack[0]) { |
|
103 | + return $this->token('newline'); |
|
104 | + } |
|
105 | + |
|
106 | + if ($indents) { |
|
107 | + array_unshift($this->indentStack, $indents); |
|
108 | + |
|
109 | + return $this->token('indent', $indents); |
|
110 | + } |
|
111 | + |
|
112 | + return $this->token('newline'); |
|
113 | + } |
|
114 | + |
|
115 | + protected function getTokenFromIndent($firstChar, $indents) |
|
116 | + { |
|
117 | + if ($this->length() && $firstChar === "\n") { |
|
118 | + return $this->token('newline'); |
|
119 | + } |
|
120 | + |
|
121 | + if (count($this->indentStack) && $indents < $this->indentStack[0]) { |
|
122 | + while (count($this->indentStack) && $indents < $this->indentStack[0]) { |
|
123 | + array_push($this->stash, $this->token('outdent')); |
|
124 | + array_shift($this->indentStack); |
|
125 | + } |
|
126 | + |
|
127 | + return array_pop($this->stash); |
|
128 | + } |
|
129 | + |
|
130 | + return $this->getWhiteSpacesTokens($indents); |
|
131 | + } |
|
132 | 132 | } |
@@ -7,284 +7,284 @@ |
||
7 | 7 | */ |
8 | 8 | abstract class Scanner extends MixinScanner |
9 | 9 | { |
10 | - /** |
|
11 | - * Helper to create tokens. |
|
12 | - */ |
|
13 | - protected function scan($regex, $type, $captureIndex = 1) |
|
14 | - { |
|
15 | - if (preg_match($regex, $this->input, $matches)) { |
|
16 | - $this->consume($matches[0]); |
|
17 | - |
|
18 | - return $this->token($type, isset($matches[$captureIndex]) && strlen($matches[$captureIndex]) > 0 ? $matches[$captureIndex] : ''); |
|
19 | - } |
|
20 | - } |
|
21 | - |
|
22 | - /** |
|
23 | - * Scan comment from input & return it if found. |
|
24 | - * |
|
25 | - * @return object|null |
|
26 | - */ |
|
27 | - protected function scanComment() |
|
28 | - { |
|
29 | - $indent = count($this->indentStack) ? $this->indentStack[0] : 0; |
|
30 | - if (preg_match('/^ *\/\/(-)?([^\n]*(\n+[ \t]{' . ($indent + 1) . ',}[^\n]*)*)/', $this->input, $matches)) { |
|
31 | - $this->consume($matches[0]); |
|
32 | - $value = isset($matches[2]) ? $matches[2] : ''; |
|
33 | - if (isset($matches[3])) { |
|
34 | - $value .= "\n"; |
|
35 | - } |
|
36 | - $token = $this->token('comment', $value); |
|
37 | - $token->buffer = '-' !== $matches[1]; |
|
38 | - |
|
39 | - return $token; |
|
40 | - } |
|
41 | - } |
|
42 | - |
|
43 | - /** |
|
44 | - * @return object |
|
45 | - */ |
|
46 | - protected function scanInterpolation() |
|
47 | - { |
|
48 | - return $this->scan('/^#{(.*?)}/', 'interpolation'); |
|
49 | - } |
|
50 | - |
|
51 | - /** |
|
52 | - * @return object |
|
53 | - */ |
|
54 | - protected function scanTag() |
|
55 | - { |
|
56 | - if (preg_match('/^(\w[-:\w]*)(\/?)/', $this->input, $matches)) { |
|
57 | - $this->consume($matches[0]); |
|
58 | - $name = $matches[1]; |
|
59 | - |
|
60 | - if (':' === substr($name, -1) && ':' !== substr($name, -2, 1)) { |
|
61 | - $name = substr($name, 0, -1); |
|
62 | - $this->defer($this->token(':')); |
|
63 | - |
|
64 | - while (' ' === substr($this->input, 0, 1)) { |
|
65 | - $this->consume(' '); |
|
66 | - } |
|
67 | - } |
|
68 | - |
|
69 | - $token = $this->token('tag', $name); |
|
70 | - $token->selfClosing = ($matches[2] === '/'); |
|
71 | - |
|
72 | - return $token; |
|
73 | - } |
|
74 | - } |
|
75 | - |
|
76 | - /** |
|
77 | - * @return object |
|
78 | - */ |
|
79 | - protected function scanFilter() |
|
80 | - { |
|
81 | - return $this->scan('/^(?<!:):(?!:)(\w+(?:-\w+)*)/', 'filter'); |
|
82 | - } |
|
83 | - |
|
84 | - /** |
|
85 | - * @return object |
|
86 | - */ |
|
87 | - protected function scanDoctype() |
|
88 | - { |
|
89 | - return $this->scan('/^(?:!!!|doctype) *([^\n]+)?/', 'doctype'); |
|
90 | - } |
|
91 | - |
|
92 | - /** |
|
93 | - * @return object |
|
94 | - */ |
|
95 | - protected function scanId() |
|
96 | - { |
|
97 | - return $this->scan('/^#([\w-]+)/', 'id'); |
|
98 | - } |
|
99 | - |
|
100 | - /** |
|
101 | - * @return object |
|
102 | - */ |
|
103 | - protected function scanClassName() |
|
104 | - { |
|
105 | - // http://www.w3.org/TR/CSS21/grammar.html#scanner |
|
106 | - // |
|
107 | - // ident: |
|
108 | - // -?{nmstart}{nmchar}* |
|
109 | - // nmstart: |
|
110 | - // [_a-z]|{nonascii}|{escape} |
|
111 | - // nonascii: |
|
112 | - // [\240-\377] |
|
113 | - // escape: |
|
114 | - // {unicode}|\\[^\r\n\f0-9a-f] |
|
115 | - // unicode: |
|
116 | - // \\{h}{1,6}(\r\n|[ \t\r\n\f])? |
|
117 | - // nmchar: |
|
118 | - // [_a-z0-9-]|{nonascii}|{escape} |
|
119 | - // |
|
120 | - // /^(-?(?!=[0-9-])(?:[_a-z0-9-]|[\240-\377]|\\{h}{1,6}(?:\r\n|[ \t\r\n\f])?|\\[^\r\n\f0-9a-f])+)/ |
|
121 | - return $this->scan('/^\.([\w-]+)/', 'class'); |
|
122 | - } |
|
123 | - |
|
124 | - /** |
|
125 | - * @return object |
|
126 | - */ |
|
127 | - protected function scanText() |
|
128 | - { |
|
129 | - return $this->scan('/^(?:\| ?| ?)?([^\n]+)/', 'text'); |
|
130 | - } |
|
131 | - |
|
132 | - /** |
|
133 | - * @return object |
|
134 | - */ |
|
135 | - protected function scanAssignment() |
|
136 | - { |
|
137 | - if (preg_match('/^(\$?\w+) += *([^;\n]+|\'[^\']+\'|"[^"]+")( *;? *)/', $this->input, $matches)) { |
|
138 | - $this->consume($matches[0]); |
|
139 | - |
|
140 | - return $this->token('code', (substr($matches[1], 0, 1) === '$' ? '' : '$') . $matches[1] . '=' . $matches[2]); |
|
141 | - } |
|
142 | - } |
|
143 | - |
|
144 | - /** |
|
145 | - * @return object |
|
146 | - */ |
|
147 | - protected function scanConditional() |
|
148 | - { |
|
149 | - if (preg_match('/^(if|unless|else if|elseif|else|while)\b([^\n]*)/', $this->input, $matches)) { |
|
150 | - $this->consume($matches[0]); |
|
151 | - |
|
152 | - /*switch ($matches[1]) { |
|
10 | + /** |
|
11 | + * Helper to create tokens. |
|
12 | + */ |
|
13 | + protected function scan($regex, $type, $captureIndex = 1) |
|
14 | + { |
|
15 | + if (preg_match($regex, $this->input, $matches)) { |
|
16 | + $this->consume($matches[0]); |
|
17 | + |
|
18 | + return $this->token($type, isset($matches[$captureIndex]) && strlen($matches[$captureIndex]) > 0 ? $matches[$captureIndex] : ''); |
|
19 | + } |
|
20 | + } |
|
21 | + |
|
22 | + /** |
|
23 | + * Scan comment from input & return it if found. |
|
24 | + * |
|
25 | + * @return object|null |
|
26 | + */ |
|
27 | + protected function scanComment() |
|
28 | + { |
|
29 | + $indent = count($this->indentStack) ? $this->indentStack[0] : 0; |
|
30 | + if (preg_match('/^ *\/\/(-)?([^\n]*(\n+[ \t]{' . ($indent + 1) . ',}[^\n]*)*)/', $this->input, $matches)) { |
|
31 | + $this->consume($matches[0]); |
|
32 | + $value = isset($matches[2]) ? $matches[2] : ''; |
|
33 | + if (isset($matches[3])) { |
|
34 | + $value .= "\n"; |
|
35 | + } |
|
36 | + $token = $this->token('comment', $value); |
|
37 | + $token->buffer = '-' !== $matches[1]; |
|
38 | + |
|
39 | + return $token; |
|
40 | + } |
|
41 | + } |
|
42 | + |
|
43 | + /** |
|
44 | + * @return object |
|
45 | + */ |
|
46 | + protected function scanInterpolation() |
|
47 | + { |
|
48 | + return $this->scan('/^#{(.*?)}/', 'interpolation'); |
|
49 | + } |
|
50 | + |
|
51 | + /** |
|
52 | + * @return object |
|
53 | + */ |
|
54 | + protected function scanTag() |
|
55 | + { |
|
56 | + if (preg_match('/^(\w[-:\w]*)(\/?)/', $this->input, $matches)) { |
|
57 | + $this->consume($matches[0]); |
|
58 | + $name = $matches[1]; |
|
59 | + |
|
60 | + if (':' === substr($name, -1) && ':' !== substr($name, -2, 1)) { |
|
61 | + $name = substr($name, 0, -1); |
|
62 | + $this->defer($this->token(':')); |
|
63 | + |
|
64 | + while (' ' === substr($this->input, 0, 1)) { |
|
65 | + $this->consume(' '); |
|
66 | + } |
|
67 | + } |
|
68 | + |
|
69 | + $token = $this->token('tag', $name); |
|
70 | + $token->selfClosing = ($matches[2] === '/'); |
|
71 | + |
|
72 | + return $token; |
|
73 | + } |
|
74 | + } |
|
75 | + |
|
76 | + /** |
|
77 | + * @return object |
|
78 | + */ |
|
79 | + protected function scanFilter() |
|
80 | + { |
|
81 | + return $this->scan('/^(?<!:):(?!:)(\w+(?:-\w+)*)/', 'filter'); |
|
82 | + } |
|
83 | + |
|
84 | + /** |
|
85 | + * @return object |
|
86 | + */ |
|
87 | + protected function scanDoctype() |
|
88 | + { |
|
89 | + return $this->scan('/^(?:!!!|doctype) *([^\n]+)?/', 'doctype'); |
|
90 | + } |
|
91 | + |
|
92 | + /** |
|
93 | + * @return object |
|
94 | + */ |
|
95 | + protected function scanId() |
|
96 | + { |
|
97 | + return $this->scan('/^#([\w-]+)/', 'id'); |
|
98 | + } |
|
99 | + |
|
100 | + /** |
|
101 | + * @return object |
|
102 | + */ |
|
103 | + protected function scanClassName() |
|
104 | + { |
|
105 | + // http://www.w3.org/TR/CSS21/grammar.html#scanner |
|
106 | + // |
|
107 | + // ident: |
|
108 | + // -?{nmstart}{nmchar}* |
|
109 | + // nmstart: |
|
110 | + // [_a-z]|{nonascii}|{escape} |
|
111 | + // nonascii: |
|
112 | + // [\240-\377] |
|
113 | + // escape: |
|
114 | + // {unicode}|\\[^\r\n\f0-9a-f] |
|
115 | + // unicode: |
|
116 | + // \\{h}{1,6}(\r\n|[ \t\r\n\f])? |
|
117 | + // nmchar: |
|
118 | + // [_a-z0-9-]|{nonascii}|{escape} |
|
119 | + // |
|
120 | + // /^(-?(?!=[0-9-])(?:[_a-z0-9-]|[\240-\377]|\\{h}{1,6}(?:\r\n|[ \t\r\n\f])?|\\[^\r\n\f0-9a-f])+)/ |
|
121 | + return $this->scan('/^\.([\w-]+)/', 'class'); |
|
122 | + } |
|
123 | + |
|
124 | + /** |
|
125 | + * @return object |
|
126 | + */ |
|
127 | + protected function scanText() |
|
128 | + { |
|
129 | + return $this->scan('/^(?:\| ?| ?)?([^\n]+)/', 'text'); |
|
130 | + } |
|
131 | + |
|
132 | + /** |
|
133 | + * @return object |
|
134 | + */ |
|
135 | + protected function scanAssignment() |
|
136 | + { |
|
137 | + if (preg_match('/^(\$?\w+) += *([^;\n]+|\'[^\']+\'|"[^"]+")( *;? *)/', $this->input, $matches)) { |
|
138 | + $this->consume($matches[0]); |
|
139 | + |
|
140 | + return $this->token('code', (substr($matches[1], 0, 1) === '$' ? '' : '$') . $matches[1] . '=' . $matches[2]); |
|
141 | + } |
|
142 | + } |
|
143 | + |
|
144 | + /** |
|
145 | + * @return object |
|
146 | + */ |
|
147 | + protected function scanConditional() |
|
148 | + { |
|
149 | + if (preg_match('/^(if|unless|else if|elseif|else|while)\b([^\n]*)/', $this->input, $matches)) { |
|
150 | + $this->consume($matches[0]); |
|
151 | + |
|
152 | + /*switch ($matches[1]) { |
|
153 | 153 | case 'if': $code = 'if (' . $matches[2] . '):'; break; |
154 | 154 | case 'unless': $code = 'if (!(' . $matches[2] . ')):'; break; |
155 | 155 | case 'else if': $code = 'elseif (' . $matches[2] . '):'; break; |
156 | 156 | case 'else': $code = 'else (' . $matches[2] . '):'; break; |
157 | 157 | }*/ |
158 | - $code = $this->normalizeCode($matches[0]); |
|
159 | - $token = $this->token('code', $code); |
|
160 | - $token->buffer = false; |
|
161 | - |
|
162 | - return $token; |
|
163 | - } |
|
164 | - } |
|
165 | - |
|
166 | - /** |
|
167 | - * @return object |
|
168 | - */ |
|
169 | - protected function scanEach() |
|
170 | - { |
|
171 | - if (preg_match('/^(?:- *)?(?:each|for) +(\w+)(?: *, *(\w+))? +in *([^\n]+)/', $this->input, $matches)) { |
|
172 | - $this->consume($matches[0]); |
|
173 | - |
|
174 | - $token = $this->token('each', $matches[1]); |
|
175 | - $token->key = $matches[2]; |
|
176 | - $token->code = $this->normalizeCode($matches[3]); |
|
177 | - |
|
178 | - return $token; |
|
179 | - } |
|
180 | - } |
|
181 | - |
|
182 | - /** |
|
183 | - * @return object |
|
184 | - */ |
|
185 | - protected function scanCustomKeyword() |
|
186 | - { |
|
187 | - if ( |
|
188 | - count($this->customKeywords) && |
|
189 | - preg_match('/^([\w-]+)([^\n]*)/', $this->input, $matches) && |
|
190 | - isset($this->customKeywords[$matches[1]]) && |
|
191 | - is_callable($this->customKeywords[$matches[1]]) |
|
192 | - ) { |
|
193 | - $this->consume($matches[0]); |
|
194 | - |
|
195 | - $token = $this->token('customKeyword', $matches[1]); |
|
196 | - $token->args = trim($matches[2]); |
|
197 | - |
|
198 | - return $token; |
|
199 | - } |
|
200 | - } |
|
201 | - |
|
202 | - /** |
|
203 | - * @return object |
|
204 | - */ |
|
205 | - protected function scanCode() |
|
206 | - { |
|
207 | - if (preg_match('/^(!?=|-)([^\n]+)/', $this->input, $matches)) { |
|
208 | - $this->consume($matches[0]); |
|
209 | - $flags = $matches[1]; |
|
210 | - $code = $this->normalizeCode($matches[2]); |
|
211 | - |
|
212 | - $token = $this->token('code', $code); |
|
213 | - $token->escape = $flags[0] === '='; |
|
214 | - $token->buffer = '=' === $flags[0] || (isset($flags[1]) && '=' === $flags[1]); |
|
215 | - |
|
216 | - return $token; |
|
217 | - } |
|
218 | - } |
|
219 | - |
|
220 | - /** |
|
221 | - * @throws \ErrorException |
|
222 | - * |
|
223 | - * @return object |
|
224 | - */ |
|
225 | - protected function scanAttributes() |
|
226 | - { |
|
227 | - if (substr($this->input, 0, 1) === '(') { |
|
228 | - // cant use ^ anchor in the regex because the pattern is recursive |
|
229 | - // but this restriction is asserted by the if above |
|
230 | - //$this->input = preg_replace('/([a-zA-Z0-9\'"\\]\\}\\)])([\t ]+[a-zA-Z])/', '$1,$2', $this->input); |
|
231 | - if (!preg_match('/\((?:"(?:\\\\.|[^"\\\\])*"|\'(?:\\\\.|[^\'\\\\])*\'|[^()\'"]++|(?R))*+\)/', $this->input, $matches)) { |
|
232 | - throw new \ErrorException('Unable to find attributes closing parenthesis.', 21); |
|
233 | - } |
|
234 | - $this->consume($matches[0]); |
|
235 | - |
|
236 | - //$str = preg_replace('/()([a-zA-Z0-9_\\x7f-\\xff\\)\\]\\}"\'])(\s+[a-zA-Z_])/', '$1,$2', $str); |
|
237 | - |
|
238 | - $token = $this->token('attributes'); |
|
239 | - $token->attributes = array(); |
|
240 | - $token->escaped = array(); |
|
241 | - $token->selfClosing = false; |
|
242 | - |
|
243 | - $parser = new Attributes($token); |
|
244 | - $parser->parseWith(substr($matches[0], 1, strlen($matches[0]) - 2)); |
|
245 | - |
|
246 | - if ($this->length() && '/' === $this->input[0]) { |
|
247 | - $this->consume(1); |
|
248 | - $token->selfClosing = true; |
|
249 | - } |
|
250 | - |
|
251 | - return $token; |
|
252 | - } |
|
253 | - } |
|
254 | - |
|
255 | - /** |
|
256 | - * @return object |
|
257 | - */ |
|
258 | - protected function scanPipelessText() |
|
259 | - { |
|
260 | - if ($this->pipeless && "\n" !== substr($this->input, 0, 1)) { |
|
261 | - $pos = strpos($this->input, "\n"); |
|
262 | - |
|
263 | - if ($pos === false) { |
|
264 | - $pos = $this->length(); |
|
265 | - } |
|
266 | - |
|
267 | - $str = substr($this->input, 0, $pos); // do not include the \n char |
|
268 | - |
|
269 | - $this->consume($str); |
|
270 | - |
|
271 | - return $this->token('text', ltrim($str)); |
|
272 | - } |
|
273 | - } |
|
274 | - |
|
275 | - /** |
|
276 | - * @return object |
|
277 | - */ |
|
278 | - protected function scanColon() |
|
279 | - { |
|
280 | - return $this->scan('/^:(?!:) */', ':'); |
|
281 | - } |
|
282 | - |
|
283 | - /** |
|
284 | - * @return object |
|
285 | - */ |
|
286 | - protected function scanAndAttributes() |
|
287 | - { |
|
288 | - return $this->scan('/^&attributes(\(((?>[^()]+|(?1))*)\))/', '&attributes', 2); |
|
289 | - } |
|
158 | + $code = $this->normalizeCode($matches[0]); |
|
159 | + $token = $this->token('code', $code); |
|
160 | + $token->buffer = false; |
|
161 | + |
|
162 | + return $token; |
|
163 | + } |
|
164 | + } |
|
165 | + |
|
166 | + /** |
|
167 | + * @return object |
|
168 | + */ |
|
169 | + protected function scanEach() |
|
170 | + { |
|
171 | + if (preg_match('/^(?:- *)?(?:each|for) +(\w+)(?: *, *(\w+))? +in *([^\n]+)/', $this->input, $matches)) { |
|
172 | + $this->consume($matches[0]); |
|
173 | + |
|
174 | + $token = $this->token('each', $matches[1]); |
|
175 | + $token->key = $matches[2]; |
|
176 | + $token->code = $this->normalizeCode($matches[3]); |
|
177 | + |
|
178 | + return $token; |
|
179 | + } |
|
180 | + } |
|
181 | + |
|
182 | + /** |
|
183 | + * @return object |
|
184 | + */ |
|
185 | + protected function scanCustomKeyword() |
|
186 | + { |
|
187 | + if ( |
|
188 | + count($this->customKeywords) && |
|
189 | + preg_match('/^([\w-]+)([^\n]*)/', $this->input, $matches) && |
|
190 | + isset($this->customKeywords[$matches[1]]) && |
|
191 | + is_callable($this->customKeywords[$matches[1]]) |
|
192 | + ) { |
|
193 | + $this->consume($matches[0]); |
|
194 | + |
|
195 | + $token = $this->token('customKeyword', $matches[1]); |
|
196 | + $token->args = trim($matches[2]); |
|
197 | + |
|
198 | + return $token; |
|
199 | + } |
|
200 | + } |
|
201 | + |
|
202 | + /** |
|
203 | + * @return object |
|
204 | + */ |
|
205 | + protected function scanCode() |
|
206 | + { |
|
207 | + if (preg_match('/^(!?=|-)([^\n]+)/', $this->input, $matches)) { |
|
208 | + $this->consume($matches[0]); |
|
209 | + $flags = $matches[1]; |
|
210 | + $code = $this->normalizeCode($matches[2]); |
|
211 | + |
|
212 | + $token = $this->token('code', $code); |
|
213 | + $token->escape = $flags[0] === '='; |
|
214 | + $token->buffer = '=' === $flags[0] || (isset($flags[1]) && '=' === $flags[1]); |
|
215 | + |
|
216 | + return $token; |
|
217 | + } |
|
218 | + } |
|
219 | + |
|
220 | + /** |
|
221 | + * @throws \ErrorException |
|
222 | + * |
|
223 | + * @return object |
|
224 | + */ |
|
225 | + protected function scanAttributes() |
|
226 | + { |
|
227 | + if (substr($this->input, 0, 1) === '(') { |
|
228 | + // cant use ^ anchor in the regex because the pattern is recursive |
|
229 | + // but this restriction is asserted by the if above |
|
230 | + //$this->input = preg_replace('/([a-zA-Z0-9\'"\\]\\}\\)])([\t ]+[a-zA-Z])/', '$1,$2', $this->input); |
|
231 | + if (!preg_match('/\((?:"(?:\\\\.|[^"\\\\])*"|\'(?:\\\\.|[^\'\\\\])*\'|[^()\'"]++|(?R))*+\)/', $this->input, $matches)) { |
|
232 | + throw new \ErrorException('Unable to find attributes closing parenthesis.', 21); |
|
233 | + } |
|
234 | + $this->consume($matches[0]); |
|
235 | + |
|
236 | + //$str = preg_replace('/()([a-zA-Z0-9_\\x7f-\\xff\\)\\]\\}"\'])(\s+[a-zA-Z_])/', '$1,$2', $str); |
|
237 | + |
|
238 | + $token = $this->token('attributes'); |
|
239 | + $token->attributes = array(); |
|
240 | + $token->escaped = array(); |
|
241 | + $token->selfClosing = false; |
|
242 | + |
|
243 | + $parser = new Attributes($token); |
|
244 | + $parser->parseWith(substr($matches[0], 1, strlen($matches[0]) - 2)); |
|
245 | + |
|
246 | + if ($this->length() && '/' === $this->input[0]) { |
|
247 | + $this->consume(1); |
|
248 | + $token->selfClosing = true; |
|
249 | + } |
|
250 | + |
|
251 | + return $token; |
|
252 | + } |
|
253 | + } |
|
254 | + |
|
255 | + /** |
|
256 | + * @return object |
|
257 | + */ |
|
258 | + protected function scanPipelessText() |
|
259 | + { |
|
260 | + if ($this->pipeless && "\n" !== substr($this->input, 0, 1)) { |
|
261 | + $pos = strpos($this->input, "\n"); |
|
262 | + |
|
263 | + if ($pos === false) { |
|
264 | + $pos = $this->length(); |
|
265 | + } |
|
266 | + |
|
267 | + $str = substr($this->input, 0, $pos); // do not include the \n char |
|
268 | + |
|
269 | + $this->consume($str); |
|
270 | + |
|
271 | + return $this->token('text', ltrim($str)); |
|
272 | + } |
|
273 | + } |
|
274 | + |
|
275 | + /** |
|
276 | + * @return object |
|
277 | + */ |
|
278 | + protected function scanColon() |
|
279 | + { |
|
280 | + return $this->scan('/^:(?!:) */', ':'); |
|
281 | + } |
|
282 | + |
|
283 | + /** |
|
284 | + * @return object |
|
285 | + */ |
|
286 | + protected function scanAndAttributes() |
|
287 | + { |
|
288 | + return $this->scan('/^&attributes(\(((?>[^()]+|(?1))*)\))/', '&attributes', 2); |
|
289 | + } |
|
290 | 290 | } |
@@ -9,304 +9,304 @@ |
||
9 | 9 | */ |
10 | 10 | class Jade extends Keywords |
11 | 11 | { |
12 | - /** |
|
13 | - * @var string |
|
14 | - */ |
|
15 | - protected $streamName = 'jade'; |
|
16 | - |
|
17 | - /** |
|
18 | - * @var array |
|
19 | - */ |
|
20 | - protected $options = array( |
|
21 | - 'allowMixedIndent' => true, |
|
22 | - 'allowMixinOverride' => true, |
|
23 | - 'cache' => null, |
|
24 | - 'classAttribute' => null, |
|
25 | - 'customKeywords' => array(), |
|
26 | - 'extension' => array('.pug', '.jade'), |
|
27 | - 'filterAutoLoad' => true, |
|
28 | - 'indentChar' => ' ', |
|
29 | - 'indentSize' => 2, |
|
30 | - 'keepBaseName' => false, |
|
31 | - 'keepNullAttributes' => false, |
|
32 | - 'phpSingleLine' => false, |
|
33 | - 'postRender' => null, |
|
34 | - 'preRender' => null, |
|
35 | - 'prettyprint' => false, |
|
36 | - 'restrictedScope' => false, |
|
37 | - 'singleQuote' => false, |
|
38 | - 'stream' => null, |
|
39 | - 'upToDateCheck' => true, |
|
40 | - ); |
|
41 | - |
|
42 | - /** |
|
43 | - * Built-in filters. |
|
44 | - * |
|
45 | - * @var array |
|
46 | - */ |
|
47 | - protected $filters = array( |
|
48 | - 'php' => 'Jade\Filter\Php', |
|
49 | - 'css' => 'Jade\Filter\Css', |
|
50 | - 'cdata' => 'Jade\Filter\Cdata', |
|
51 | - 'escaped' => 'Jade\Filter\Escaped', |
|
52 | - 'javascript' => 'Jade\Filter\Javascript', |
|
53 | - ); |
|
54 | - |
|
55 | - /** |
|
56 | - * @var array |
|
57 | - */ |
|
58 | - protected $sharedVariables = array(); |
|
59 | - |
|
60 | - /** |
|
61 | - * Merge local options with constructor $options. |
|
62 | - * |
|
63 | - * @param array $options |
|
64 | - */ |
|
65 | - public function __construct(array $options = array()) |
|
66 | - { |
|
67 | - if (is_null($this->options['stream'])) { |
|
68 | - $this->options['stream'] = $this->streamName . '.stream'; |
|
69 | - } |
|
70 | - $this->options = array_merge($this->options, $options); |
|
71 | - } |
|
72 | - |
|
73 | - /** |
|
74 | - * Returns true if suhosin extension is loaded and the stream name |
|
75 | - * is missing in the executor include whitelist. |
|
76 | - * Returns false in any other case. |
|
77 | - * |
|
78 | - * @return bool |
|
79 | - */ |
|
80 | - protected function whiteListNeeded($extension) |
|
81 | - { |
|
82 | - return extension_loaded($extension) && |
|
83 | - false === strpos( |
|
84 | - ini_get($extension . '.executor.include.whitelist'), |
|
85 | - $this->options['stream'] |
|
86 | - ); |
|
87 | - } |
|
88 | - |
|
89 | - /** |
|
90 | - * Returns list of requirements in an array identified by keys. |
|
91 | - * For each of them, the value can be true if the requirement is |
|
92 | - * fullfilled, false else. |
|
93 | - * |
|
94 | - * If a requirement name is specified, returns only the matching |
|
95 | - * boolean value for this requirement. |
|
96 | - * |
|
97 | - * @param string name |
|
98 | - * |
|
99 | - * @throws \InvalidArgumentException |
|
100 | - * |
|
101 | - * @return array|bool |
|
102 | - */ |
|
103 | - public function requirements($name = null) |
|
104 | - { |
|
105 | - $requirements = array( |
|
106 | - 'streamWhiteListed' => !$this->whiteListNeeded('suhosin'), |
|
107 | - 'cacheFolderExists' => $this->options['cache'] && is_dir($this->options['cache']), |
|
108 | - 'cacheFolderIsWritable' => $this->options['cache'] && is_writable($this->options['cache']), |
|
109 | - ); |
|
110 | - |
|
111 | - if ($name) { |
|
112 | - if (!isset($requirements[$name])) { |
|
113 | - throw new \InvalidArgumentException($name . ' is not in the requirements list (' . implode(', ', array_keys($requirements)) . ')', 19); |
|
114 | - } |
|
115 | - |
|
116 | - return $requirements[$name]; |
|
117 | - } |
|
118 | - |
|
119 | - return $requirements; |
|
120 | - } |
|
121 | - |
|
122 | - /** |
|
123 | - * Get standard or custom option, return the previously setted value or the default value else. |
|
124 | - * |
|
125 | - * Throw a invalid argument exception if the option does not exists. |
|
126 | - * |
|
127 | - * @param string name |
|
128 | - * |
|
129 | - * @throws \InvalidArgumentException |
|
130 | - */ |
|
131 | - public function getOption($name) |
|
132 | - { |
|
133 | - if (!array_key_exists($name, $this->options)) { |
|
134 | - throw new \InvalidArgumentException("$name is not a valid option name.", 2); |
|
135 | - } |
|
136 | - |
|
137 | - return $this->options[$name]; |
|
138 | - } |
|
139 | - |
|
140 | - /** |
|
141 | - * Set one standard option (listed in $this->options). |
|
142 | - * |
|
143 | - * @param string name |
|
144 | - * @param mixed option value |
|
145 | - * |
|
146 | - * @throws \InvalidArgumentException |
|
147 | - * |
|
148 | - * @return $this |
|
149 | - */ |
|
150 | - public function setOption($name, $value) |
|
151 | - { |
|
152 | - if (!array_key_exists($name, $this->options)) { |
|
153 | - throw new \InvalidArgumentException("$name is not a valid option name.", 3); |
|
154 | - } |
|
155 | - |
|
156 | - $this->options[$name] = $value; |
|
157 | - |
|
158 | - return $this; |
|
159 | - } |
|
160 | - |
|
161 | - /** |
|
162 | - * Set multiple standard options. |
|
163 | - * |
|
164 | - * @param array option list |
|
165 | - * |
|
166 | - * @throws \InvalidArgumentException |
|
167 | - * |
|
168 | - * @return $this |
|
169 | - */ |
|
170 | - public function setOptions($options) |
|
171 | - { |
|
172 | - foreach ($options as $name => $value) { |
|
173 | - $this->setOption($name, $value); |
|
174 | - } |
|
175 | - |
|
176 | - return $this; |
|
177 | - } |
|
178 | - |
|
179 | - /** |
|
180 | - * Set one custom option. |
|
181 | - * |
|
182 | - * @param string name |
|
183 | - * @param mixed option value |
|
184 | - * |
|
185 | - * @return $this |
|
186 | - */ |
|
187 | - public function setCustomOption($name, $value) |
|
188 | - { |
|
189 | - $this->options[$name] = $value; |
|
190 | - |
|
191 | - return $this; |
|
192 | - } |
|
193 | - |
|
194 | - /** |
|
195 | - * Set multiple custom options. |
|
196 | - * |
|
197 | - * @param array options list |
|
198 | - * |
|
199 | - * @return $this |
|
200 | - */ |
|
201 | - public function setCustomOptions(array $options) |
|
202 | - { |
|
203 | - $this->options = array_merge($this->options, $options); |
|
204 | - |
|
205 | - return $this; |
|
206 | - } |
|
207 | - |
|
208 | - /** |
|
209 | - * Compile PHP code from a Pug input or a Pug file. |
|
210 | - * |
|
211 | - * @param string input |
|
212 | - * |
|
213 | - * @throws \Exception |
|
214 | - * |
|
215 | - * @return string |
|
216 | - */ |
|
217 | - public function compile($input) |
|
218 | - { |
|
219 | - $parser = new Parser($input, null, $this->options); |
|
220 | - $compiler = new Compiler($this->options, $this->filters, $parser->getFilename()); |
|
221 | - $php = $compiler->compile($parser->parse()); |
|
222 | - if (version_compare(PHP_VERSION, '7.0.0') < 0) { |
|
223 | - $php = preg_replace_callback('/(' . preg_quote('\\Jade\\Compiler::getPropertyFromAnything', '/') . '\\(((?>[^()]+)|(?-2))*\\))[ \t]*(\\(((?>[^()]+)|(?-2))*\\))/', function ($match) { |
|
224 | - return 'call_user_func(' . $match[1] . ', ' . $match[4] . ')'; |
|
225 | - }, $php); |
|
226 | - } |
|
227 | - $postRender = $this->getOption('postRender'); |
|
228 | - if (is_callable($postRender)) { |
|
229 | - $php = call_user_func($postRender, $php); |
|
230 | - } |
|
231 | - |
|
232 | - return $php; |
|
233 | - } |
|
234 | - |
|
235 | - /** |
|
236 | - * Compile HTML code from a Pug input or a Pug file. |
|
237 | - * |
|
238 | - * @param sring Pug input or file |
|
239 | - * @param array vars to pass to the view |
|
240 | - * |
|
241 | - * @throws \Exception |
|
242 | - * |
|
243 | - * @return string |
|
244 | - */ |
|
245 | - public function render($input, array $vars = array()) |
|
246 | - { |
|
247 | - $file = $this->options['cache'] |
|
248 | - ? $this->cache($input) |
|
249 | - : $this->stream($this->compile($input)); |
|
250 | - |
|
251 | - extract(array_merge($this->sharedVariables, $vars)); |
|
252 | - ob_start(); |
|
253 | - try { |
|
254 | - include $file; |
|
255 | - } catch (\Exception $e) { |
|
256 | - ob_end_clean(); |
|
257 | - throw $e; |
|
258 | - } |
|
259 | - |
|
260 | - return ob_get_clean(); |
|
261 | - } |
|
262 | - |
|
263 | - /** |
|
264 | - * Create a stream wrapper to allow |
|
265 | - * the possibility to add $scope variables. |
|
266 | - * |
|
267 | - * @param string input |
|
268 | - * |
|
269 | - * @throws \ErrorException |
|
270 | - * |
|
271 | - * @return string |
|
272 | - */ |
|
273 | - public function stream($input) |
|
274 | - { |
|
275 | - if ($this->whiteListNeeded('suhosin')) { |
|
276 | - throw new \ErrorException('To run Pug.php on the fly, add "' . $this->options['stream'] . '" to the "suhosin.executor.include.whitelist" settings in your php.ini file.', 4); |
|
277 | - } |
|
278 | - |
|
279 | - if (!in_array($this->options['stream'], stream_get_wrappers())) { |
|
280 | - stream_wrapper_register($this->options['stream'], 'Jade\Stream\Template'); |
|
281 | - } |
|
282 | - |
|
283 | - return $this->options['stream'] . '://data;' . $input; |
|
284 | - } |
|
285 | - |
|
286 | - /** |
|
287 | - * Add a variable or an array of variables to be shared with all templates that will be rendered |
|
288 | - * by the instance of Pug. |
|
289 | - * |
|
290 | - * @param array|string $variables|$key an associatives array of variable names and values, or a |
|
291 | - * variable name if you wish to sahre only one |
|
292 | - * @param mixed $value if you pass an array as first argument, the second |
|
293 | - * argument will be ignored, else it will used as the |
|
294 | - * variable value for the variable name you passed as first |
|
295 | - * argument |
|
296 | - */ |
|
297 | - public function share($variables, $value = null) |
|
298 | - { |
|
299 | - if (!is_array($variables)) { |
|
300 | - $variables = array(strval($variables) => $value); |
|
301 | - } |
|
302 | - $this->sharedVariables = array_merge($this->sharedVariables, $variables); |
|
303 | - } |
|
304 | - |
|
305 | - /** |
|
306 | - * Remove all previously set shared variables. |
|
307 | - */ |
|
308 | - public function resetSharedVariables() |
|
309 | - { |
|
310 | - $this->sharedVariables = array(); |
|
311 | - } |
|
12 | + /** |
|
13 | + * @var string |
|
14 | + */ |
|
15 | + protected $streamName = 'jade'; |
|
16 | + |
|
17 | + /** |
|
18 | + * @var array |
|
19 | + */ |
|
20 | + protected $options = array( |
|
21 | + 'allowMixedIndent' => true, |
|
22 | + 'allowMixinOverride' => true, |
|
23 | + 'cache' => null, |
|
24 | + 'classAttribute' => null, |
|
25 | + 'customKeywords' => array(), |
|
26 | + 'extension' => array('.pug', '.jade'), |
|
27 | + 'filterAutoLoad' => true, |
|
28 | + 'indentChar' => ' ', |
|
29 | + 'indentSize' => 2, |
|
30 | + 'keepBaseName' => false, |
|
31 | + 'keepNullAttributes' => false, |
|
32 | + 'phpSingleLine' => false, |
|
33 | + 'postRender' => null, |
|
34 | + 'preRender' => null, |
|
35 | + 'prettyprint' => false, |
|
36 | + 'restrictedScope' => false, |
|
37 | + 'singleQuote' => false, |
|
38 | + 'stream' => null, |
|
39 | + 'upToDateCheck' => true, |
|
40 | + ); |
|
41 | + |
|
42 | + /** |
|
43 | + * Built-in filters. |
|
44 | + * |
|
45 | + * @var array |
|
46 | + */ |
|
47 | + protected $filters = array( |
|
48 | + 'php' => 'Jade\Filter\Php', |
|
49 | + 'css' => 'Jade\Filter\Css', |
|
50 | + 'cdata' => 'Jade\Filter\Cdata', |
|
51 | + 'escaped' => 'Jade\Filter\Escaped', |
|
52 | + 'javascript' => 'Jade\Filter\Javascript', |
|
53 | + ); |
|
54 | + |
|
55 | + /** |
|
56 | + * @var array |
|
57 | + */ |
|
58 | + protected $sharedVariables = array(); |
|
59 | + |
|
60 | + /** |
|
61 | + * Merge local options with constructor $options. |
|
62 | + * |
|
63 | + * @param array $options |
|
64 | + */ |
|
65 | + public function __construct(array $options = array()) |
|
66 | + { |
|
67 | + if (is_null($this->options['stream'])) { |
|
68 | + $this->options['stream'] = $this->streamName . '.stream'; |
|
69 | + } |
|
70 | + $this->options = array_merge($this->options, $options); |
|
71 | + } |
|
72 | + |
|
73 | + /** |
|
74 | + * Returns true if suhosin extension is loaded and the stream name |
|
75 | + * is missing in the executor include whitelist. |
|
76 | + * Returns false in any other case. |
|
77 | + * |
|
78 | + * @return bool |
|
79 | + */ |
|
80 | + protected function whiteListNeeded($extension) |
|
81 | + { |
|
82 | + return extension_loaded($extension) && |
|
83 | + false === strpos( |
|
84 | + ini_get($extension . '.executor.include.whitelist'), |
|
85 | + $this->options['stream'] |
|
86 | + ); |
|
87 | + } |
|
88 | + |
|
89 | + /** |
|
90 | + * Returns list of requirements in an array identified by keys. |
|
91 | + * For each of them, the value can be true if the requirement is |
|
92 | + * fullfilled, false else. |
|
93 | + * |
|
94 | + * If a requirement name is specified, returns only the matching |
|
95 | + * boolean value for this requirement. |
|
96 | + * |
|
97 | + * @param string name |
|
98 | + * |
|
99 | + * @throws \InvalidArgumentException |
|
100 | + * |
|
101 | + * @return array|bool |
|
102 | + */ |
|
103 | + public function requirements($name = null) |
|
104 | + { |
|
105 | + $requirements = array( |
|
106 | + 'streamWhiteListed' => !$this->whiteListNeeded('suhosin'), |
|
107 | + 'cacheFolderExists' => $this->options['cache'] && is_dir($this->options['cache']), |
|
108 | + 'cacheFolderIsWritable' => $this->options['cache'] && is_writable($this->options['cache']), |
|
109 | + ); |
|
110 | + |
|
111 | + if ($name) { |
|
112 | + if (!isset($requirements[$name])) { |
|
113 | + throw new \InvalidArgumentException($name . ' is not in the requirements list (' . implode(', ', array_keys($requirements)) . ')', 19); |
|
114 | + } |
|
115 | + |
|
116 | + return $requirements[$name]; |
|
117 | + } |
|
118 | + |
|
119 | + return $requirements; |
|
120 | + } |
|
121 | + |
|
122 | + /** |
|
123 | + * Get standard or custom option, return the previously setted value or the default value else. |
|
124 | + * |
|
125 | + * Throw a invalid argument exception if the option does not exists. |
|
126 | + * |
|
127 | + * @param string name |
|
128 | + * |
|
129 | + * @throws \InvalidArgumentException |
|
130 | + */ |
|
131 | + public function getOption($name) |
|
132 | + { |
|
133 | + if (!array_key_exists($name, $this->options)) { |
|
134 | + throw new \InvalidArgumentException("$name is not a valid option name.", 2); |
|
135 | + } |
|
136 | + |
|
137 | + return $this->options[$name]; |
|
138 | + } |
|
139 | + |
|
140 | + /** |
|
141 | + * Set one standard option (listed in $this->options). |
|
142 | + * |
|
143 | + * @param string name |
|
144 | + * @param mixed option value |
|
145 | + * |
|
146 | + * @throws \InvalidArgumentException |
|
147 | + * |
|
148 | + * @return $this |
|
149 | + */ |
|
150 | + public function setOption($name, $value) |
|
151 | + { |
|
152 | + if (!array_key_exists($name, $this->options)) { |
|
153 | + throw new \InvalidArgumentException("$name is not a valid option name.", 3); |
|
154 | + } |
|
155 | + |
|
156 | + $this->options[$name] = $value; |
|
157 | + |
|
158 | + return $this; |
|
159 | + } |
|
160 | + |
|
161 | + /** |
|
162 | + * Set multiple standard options. |
|
163 | + * |
|
164 | + * @param array option list |
|
165 | + * |
|
166 | + * @throws \InvalidArgumentException |
|
167 | + * |
|
168 | + * @return $this |
|
169 | + */ |
|
170 | + public function setOptions($options) |
|
171 | + { |
|
172 | + foreach ($options as $name => $value) { |
|
173 | + $this->setOption($name, $value); |
|
174 | + } |
|
175 | + |
|
176 | + return $this; |
|
177 | + } |
|
178 | + |
|
179 | + /** |
|
180 | + * Set one custom option. |
|
181 | + * |
|
182 | + * @param string name |
|
183 | + * @param mixed option value |
|
184 | + * |
|
185 | + * @return $this |
|
186 | + */ |
|
187 | + public function setCustomOption($name, $value) |
|
188 | + { |
|
189 | + $this->options[$name] = $value; |
|
190 | + |
|
191 | + return $this; |
|
192 | + } |
|
193 | + |
|
194 | + /** |
|
195 | + * Set multiple custom options. |
|
196 | + * |
|
197 | + * @param array options list |
|
198 | + * |
|
199 | + * @return $this |
|
200 | + */ |
|
201 | + public function setCustomOptions(array $options) |
|
202 | + { |
|
203 | + $this->options = array_merge($this->options, $options); |
|
204 | + |
|
205 | + return $this; |
|
206 | + } |
|
207 | + |
|
208 | + /** |
|
209 | + * Compile PHP code from a Pug input or a Pug file. |
|
210 | + * |
|
211 | + * @param string input |
|
212 | + * |
|
213 | + * @throws \Exception |
|
214 | + * |
|
215 | + * @return string |
|
216 | + */ |
|
217 | + public function compile($input) |
|
218 | + { |
|
219 | + $parser = new Parser($input, null, $this->options); |
|
220 | + $compiler = new Compiler($this->options, $this->filters, $parser->getFilename()); |
|
221 | + $php = $compiler->compile($parser->parse()); |
|
222 | + if (version_compare(PHP_VERSION, '7.0.0') < 0) { |
|
223 | + $php = preg_replace_callback('/(' . preg_quote('\\Jade\\Compiler::getPropertyFromAnything', '/') . '\\(((?>[^()]+)|(?-2))*\\))[ \t]*(\\(((?>[^()]+)|(?-2))*\\))/', function ($match) { |
|
224 | + return 'call_user_func(' . $match[1] . ', ' . $match[4] . ')'; |
|
225 | + }, $php); |
|
226 | + } |
|
227 | + $postRender = $this->getOption('postRender'); |
|
228 | + if (is_callable($postRender)) { |
|
229 | + $php = call_user_func($postRender, $php); |
|
230 | + } |
|
231 | + |
|
232 | + return $php; |
|
233 | + } |
|
234 | + |
|
235 | + /** |
|
236 | + * Compile HTML code from a Pug input or a Pug file. |
|
237 | + * |
|
238 | + * @param sring Pug input or file |
|
239 | + * @param array vars to pass to the view |
|
240 | + * |
|
241 | + * @throws \Exception |
|
242 | + * |
|
243 | + * @return string |
|
244 | + */ |
|
245 | + public function render($input, array $vars = array()) |
|
246 | + { |
|
247 | + $file = $this->options['cache'] |
|
248 | + ? $this->cache($input) |
|
249 | + : $this->stream($this->compile($input)); |
|
250 | + |
|
251 | + extract(array_merge($this->sharedVariables, $vars)); |
|
252 | + ob_start(); |
|
253 | + try { |
|
254 | + include $file; |
|
255 | + } catch (\Exception $e) { |
|
256 | + ob_end_clean(); |
|
257 | + throw $e; |
|
258 | + } |
|
259 | + |
|
260 | + return ob_get_clean(); |
|
261 | + } |
|
262 | + |
|
263 | + /** |
|
264 | + * Create a stream wrapper to allow |
|
265 | + * the possibility to add $scope variables. |
|
266 | + * |
|
267 | + * @param string input |
|
268 | + * |
|
269 | + * @throws \ErrorException |
|
270 | + * |
|
271 | + * @return string |
|
272 | + */ |
|
273 | + public function stream($input) |
|
274 | + { |
|
275 | + if ($this->whiteListNeeded('suhosin')) { |
|
276 | + throw new \ErrorException('To run Pug.php on the fly, add "' . $this->options['stream'] . '" to the "suhosin.executor.include.whitelist" settings in your php.ini file.', 4); |
|
277 | + } |
|
278 | + |
|
279 | + if (!in_array($this->options['stream'], stream_get_wrappers())) { |
|
280 | + stream_wrapper_register($this->options['stream'], 'Jade\Stream\Template'); |
|
281 | + } |
|
282 | + |
|
283 | + return $this->options['stream'] . '://data;' . $input; |
|
284 | + } |
|
285 | + |
|
286 | + /** |
|
287 | + * Add a variable or an array of variables to be shared with all templates that will be rendered |
|
288 | + * by the instance of Pug. |
|
289 | + * |
|
290 | + * @param array|string $variables|$key an associatives array of variable names and values, or a |
|
291 | + * variable name if you wish to sahre only one |
|
292 | + * @param mixed $value if you pass an array as first argument, the second |
|
293 | + * argument will be ignored, else it will used as the |
|
294 | + * variable value for the variable name you passed as first |
|
295 | + * argument |
|
296 | + */ |
|
297 | + public function share($variables, $value = null) |
|
298 | + { |
|
299 | + if (!is_array($variables)) { |
|
300 | + $variables = array(strval($variables) => $value); |
|
301 | + } |
|
302 | + $this->sharedVariables = array_merge($this->sharedVariables, $variables); |
|
303 | + } |
|
304 | + |
|
305 | + /** |
|
306 | + * Remove all previously set shared variables. |
|
307 | + */ |
|
308 | + public function resetSharedVariables() |
|
309 | + { |
|
310 | + $this->sharedVariables = array(); |
|
311 | + } |
|
312 | 312 | } |