1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
require_once( dirname(__FILE__).'/Cache.php'); |
4
|
|
|
|
5
|
|
|
/** |
6
|
|
|
* Class for parsing and compiling less files into css |
7
|
|
|
* |
8
|
|
|
* @package Less |
9
|
|
|
* @subpackage parser |
10
|
|
|
* |
11
|
|
|
*/ |
12
|
|
|
class Less_Parser{ |
13
|
|
|
|
14
|
|
|
|
15
|
|
|
/** |
16
|
|
|
* Default parser options |
17
|
|
|
*/ |
18
|
|
|
public static $default_options = array( |
19
|
|
|
'compress' => false, // option - whether to compress |
20
|
|
|
'strictUnits' => false, // whether units need to evaluate correctly |
21
|
|
|
'strictMath' => false, // whether math has to be within parenthesis |
22
|
|
|
'relativeUrls' => true, // option - whether to adjust URL's to be relative |
23
|
|
|
'urlArgs' => array(), // whether to add args into url tokens |
24
|
|
|
'numPrecision' => 8, |
25
|
|
|
|
26
|
|
|
'import_dirs' => array(), |
27
|
|
|
'import_callback' => null, |
28
|
|
|
'cache_dir' => null, |
29
|
|
|
'cache_method' => 'php', // false, 'serialize', 'php', 'var_export', 'callback'; |
30
|
|
|
'cache_callback_get' => null, |
31
|
|
|
'cache_callback_set' => null, |
32
|
|
|
|
33
|
|
|
'sourceMap' => false, // whether to output a source map |
34
|
|
|
'sourceMapBasepath' => null, |
35
|
|
|
'sourceMapWriteTo' => null, |
36
|
|
|
'sourceMapURL' => null, |
37
|
|
|
|
38
|
|
|
'plugins' => array(), |
39
|
|
|
|
40
|
|
|
); |
41
|
|
|
|
42
|
|
|
public static $options = array(); |
43
|
|
|
|
44
|
|
|
|
45
|
|
|
private $input; // Less input string |
46
|
|
|
private $input_len; // input string length |
47
|
|
|
private $pos; // current index in `input` |
48
|
|
|
private $saveStack = array(); // holds state for backtracking |
49
|
|
|
private $furthest; |
50
|
|
|
private $mb_internal_encoding = ''; // for remember exists value of mbstring.internal_encoding |
51
|
|
|
|
52
|
|
|
/** |
53
|
|
|
* @var Less_Environment |
54
|
|
|
*/ |
55
|
|
|
private $env; |
56
|
|
|
|
57
|
|
|
protected $rules = array(); |
58
|
|
|
|
59
|
|
|
private static $imports = array(); |
60
|
|
|
|
61
|
|
|
public static $has_extends = false; |
62
|
|
|
|
63
|
|
|
public static $next_id = 0; |
64
|
|
|
|
65
|
|
|
/** |
66
|
|
|
* Filename to contents of all parsed the files |
67
|
|
|
* |
68
|
|
|
* @var array |
69
|
|
|
*/ |
70
|
|
|
public static $contentsMap = array(); |
71
|
|
|
|
72
|
|
|
|
73
|
|
|
/** |
74
|
|
|
* @param Less_Environment|array|null $env |
75
|
|
|
*/ |
76
|
|
|
public function __construct( $env = null ){ |
77
|
|
|
|
78
|
|
|
// Top parser on an import tree must be sure there is one "env" |
79
|
|
|
// which will then be passed around by reference. |
80
|
|
|
if( $env instanceof Less_Environment ){ |
81
|
|
|
$this->env = $env; |
82
|
|
|
}else{ |
83
|
|
|
$this->SetOptions(Less_Parser::$default_options); |
84
|
|
|
$this->Reset( $env ); |
85
|
|
|
} |
86
|
|
|
|
87
|
|
|
// mbstring.func_overload > 1 bugfix |
88
|
|
|
// The encoding value must be set for each source file, |
89
|
|
|
// therefore, to conserve resources and improve the speed of this design is taken here |
90
|
|
|
if (ini_get('mbstring.func_overload')) { |
91
|
|
|
$this->mb_internal_encoding = ini_get('mbstring.internal_encoding'); |
92
|
|
|
@ini_set('mbstring.internal_encoding', 'ascii'); |
|
|
|
|
93
|
|
|
} |
94
|
|
|
|
95
|
|
|
} |
96
|
|
|
|
97
|
|
|
|
98
|
|
|
/** |
99
|
|
|
* Reset the parser state completely |
100
|
|
|
* |
101
|
|
|
*/ |
102
|
|
|
public function Reset( $options = null ){ |
103
|
|
|
$this->rules = array(); |
104
|
|
|
self::$imports = array(); |
105
|
|
|
self::$has_extends = false; |
106
|
|
|
self::$imports = array(); |
107
|
|
|
self::$contentsMap = array(); |
108
|
|
|
|
109
|
|
|
$this->env = new Less_Environment($options); |
110
|
|
|
$this->env->Init(); |
111
|
|
|
|
112
|
|
|
//set new options |
113
|
|
|
if( is_array($options) ){ |
114
|
|
|
$this->SetOptions(Less_Parser::$default_options); |
115
|
|
|
$this->SetOptions($options); |
116
|
|
|
} |
117
|
|
|
} |
118
|
|
|
|
119
|
|
|
/** |
120
|
|
|
* Set one or more compiler options |
121
|
|
|
* options: import_dirs, cache_dir, cache_method |
122
|
|
|
* |
123
|
|
|
*/ |
124
|
|
|
public function SetOptions( $options ){ |
125
|
|
|
foreach($options as $option => $value){ |
126
|
|
|
$this->SetOption($option,$value); |
127
|
|
|
} |
128
|
|
|
} |
129
|
|
|
|
130
|
|
|
/** |
131
|
|
|
* Set one compiler option |
132
|
|
|
* |
133
|
|
|
*/ |
134
|
|
|
public function SetOption($option,$value){ |
135
|
|
|
|
136
|
|
|
switch($option){ |
137
|
|
|
|
138
|
|
|
case 'import_dirs': |
139
|
|
|
$this->SetImportDirs($value); |
140
|
|
|
return; |
141
|
|
|
|
142
|
|
|
case 'cache_dir': |
143
|
|
|
if( is_string($value) ){ |
144
|
|
|
Less_Cache::SetCacheDir($value); |
145
|
|
|
Less_Cache::CheckCacheDir(); |
146
|
|
|
} |
147
|
|
|
return; |
148
|
|
|
} |
149
|
|
|
|
150
|
|
|
Less_Parser::$options[$option] = $value; |
151
|
|
|
} |
152
|
|
|
|
153
|
|
|
/** |
154
|
|
|
* Registers a new custom function |
155
|
|
|
* |
156
|
|
|
* @param string $name function name |
157
|
|
|
* @param callable $callback callback |
158
|
|
|
*/ |
159
|
|
|
public function registerFunction($name, $callback) { |
160
|
|
|
$this->env->functions[$name] = $callback; |
161
|
|
|
} |
162
|
|
|
|
163
|
|
|
/** |
164
|
|
|
* Removed an already registered function |
165
|
|
|
* |
166
|
|
|
* @param string $name function name |
167
|
|
|
*/ |
168
|
|
|
public function unregisterFunction($name) { |
169
|
|
|
if( isset($this->env->functions[$name]) ) |
170
|
|
|
unset($this->env->functions[$name]); |
171
|
|
|
} |
172
|
|
|
|
173
|
|
|
|
174
|
|
|
/** |
175
|
|
|
* Get the current css buffer |
176
|
|
|
* |
177
|
|
|
* @return string |
178
|
|
|
*/ |
179
|
|
|
public function getCss(){ |
180
|
|
|
|
181
|
|
|
$precision = ini_get('precision'); |
182
|
|
|
@ini_set('precision',16); |
183
|
|
|
$locale = setlocale(LC_NUMERIC, 0); |
184
|
|
|
setlocale(LC_NUMERIC, "C"); |
185
|
|
|
|
186
|
|
|
try { |
187
|
|
|
|
188
|
|
|
$root = new Less_Tree_Ruleset(array(), $this->rules ); |
189
|
|
|
$root->root = true; |
190
|
|
|
$root->firstRoot = true; |
191
|
|
|
|
192
|
|
|
|
193
|
|
|
$this->PreVisitors($root); |
194
|
|
|
|
195
|
|
|
self::$has_extends = false; |
196
|
|
|
$evaldRoot = $root->compile($this->env); |
197
|
|
|
|
198
|
|
|
|
199
|
|
|
|
200
|
|
|
$this->PostVisitors($evaldRoot); |
201
|
|
|
|
202
|
|
|
if( Less_Parser::$options['sourceMap'] ){ |
203
|
|
|
$generator = new Less_SourceMap_Generator($evaldRoot, Less_Parser::$contentsMap, Less_Parser::$options ); |
204
|
|
|
// will also save file |
205
|
|
|
// FIXME: should happen somewhere else? |
206
|
|
|
$css = $generator->generateCSS(); |
207
|
|
|
}else{ |
208
|
|
|
$css = $evaldRoot->toCSS(); |
209
|
|
|
} |
210
|
|
|
|
211
|
|
|
if( Less_Parser::$options['compress'] ){ |
212
|
|
|
$css = preg_replace('/(^(\s)+)|((\s)+$)/', '', $css); |
213
|
|
|
} |
214
|
|
|
|
215
|
|
|
} catch (Exception $exc) { |
216
|
|
|
// Intentional fall-through so we can reset environment |
217
|
|
|
} |
218
|
|
|
|
219
|
|
|
//reset php settings |
220
|
|
|
@ini_set('precision',$precision); |
221
|
|
|
setlocale(LC_NUMERIC, $locale); |
222
|
|
|
|
223
|
|
|
// If you previously defined $this->mb_internal_encoding |
224
|
|
|
// is required to return the encoding as it was before |
225
|
|
|
if ($this->mb_internal_encoding != '') { |
226
|
|
|
@ini_set("mbstring.internal_encoding", $this->mb_internal_encoding); |
|
|
|
|
227
|
|
|
$this->mb_internal_encoding = ''; |
228
|
|
|
} |
229
|
|
|
|
230
|
|
|
// Rethrow exception after we handled resetting the environment |
231
|
|
|
if (!empty($exc)) { |
232
|
|
|
throw $exc; |
233
|
|
|
} |
234
|
|
|
|
235
|
|
|
|
236
|
|
|
|
237
|
|
|
return $css; |
238
|
|
|
} |
239
|
|
|
|
240
|
|
|
/** |
241
|
|
|
* Run pre-compile visitors |
242
|
|
|
* |
243
|
|
|
*/ |
244
|
|
|
private function PreVisitors($root){ |
245
|
|
|
|
246
|
|
|
if( Less_Parser::$options['plugins'] ){ |
247
|
|
|
foreach(Less_Parser::$options['plugins'] as $plugin){ |
248
|
|
|
if( !empty($plugin->isPreEvalVisitor) ){ |
249
|
|
|
$plugin->run($root); |
250
|
|
|
} |
251
|
|
|
} |
252
|
|
|
} |
253
|
|
|
} |
254
|
|
|
|
255
|
|
|
|
256
|
|
|
/** |
257
|
|
|
* Run post-compile visitors |
258
|
|
|
* |
259
|
|
|
*/ |
260
|
|
|
private function PostVisitors($evaldRoot){ |
261
|
|
|
|
262
|
|
|
$visitors = array(); |
263
|
|
|
$visitors[] = new Less_Visitor_joinSelector(); |
264
|
|
|
if( self::$has_extends ){ |
265
|
|
|
$visitors[] = new Less_Visitor_processExtends(); |
266
|
|
|
} |
267
|
|
|
$visitors[] = new Less_Visitor_toCSS(); |
268
|
|
|
|
269
|
|
|
|
270
|
|
|
if( Less_Parser::$options['plugins'] ){ |
271
|
|
|
foreach(Less_Parser::$options['plugins'] as $plugin){ |
272
|
|
|
if( property_exists($plugin,'isPreEvalVisitor') && $plugin->isPreEvalVisitor ){ |
273
|
|
|
continue; |
274
|
|
|
} |
275
|
|
|
|
276
|
|
|
if( property_exists($plugin,'isPreVisitor') && $plugin->isPreVisitor ){ |
277
|
|
|
array_unshift( $visitors, $plugin); |
278
|
|
|
}else{ |
279
|
|
|
$visitors[] = $plugin; |
280
|
|
|
} |
281
|
|
|
} |
282
|
|
|
} |
283
|
|
|
|
284
|
|
|
|
285
|
|
|
for($i = 0; $i < count($visitors); $i++ ){ |
|
|
|
|
286
|
|
|
$visitors[$i]->run($evaldRoot); |
287
|
|
|
} |
288
|
|
|
|
289
|
|
|
} |
290
|
|
|
|
291
|
|
|
|
292
|
|
|
/** |
293
|
|
|
* Parse a Less string into css |
294
|
|
|
* |
295
|
|
|
* @param string $str The string to convert |
296
|
|
|
* @param string $uri_root The url of the file |
297
|
|
|
* @return Less_Tree_Ruleset|Less_Parser |
298
|
|
|
*/ |
299
|
|
|
public function parse( $str, $file_uri = null ){ |
300
|
|
|
|
301
|
|
|
if( !$file_uri ){ |
302
|
|
|
$uri_root = ''; |
303
|
|
|
$filename = 'anonymous-file-'.Less_Parser::$next_id++.'.less'; |
304
|
|
|
}else{ |
305
|
|
|
$file_uri = self::WinPath($file_uri); |
306
|
|
|
$filename = $file_uri; |
307
|
|
|
$uri_root = dirname($file_uri); |
308
|
|
|
} |
309
|
|
|
|
310
|
|
|
$previousFileInfo = $this->env->currentFileInfo; |
311
|
|
|
$uri_root = self::WinPath($uri_root); |
312
|
|
|
$this->SetFileInfo($filename, $uri_root); |
313
|
|
|
|
314
|
|
|
$this->input = $str; |
315
|
|
|
$this->_parse(); |
316
|
|
|
|
317
|
|
|
if( $previousFileInfo ){ |
318
|
|
|
$this->env->currentFileInfo = $previousFileInfo; |
319
|
|
|
} |
320
|
|
|
|
321
|
|
|
return $this; |
322
|
|
|
} |
323
|
|
|
|
324
|
|
|
|
325
|
|
|
/** |
326
|
|
|
* Parse a Less string from a given file |
327
|
|
|
* |
328
|
|
|
* @throws Less_Exception_Parser |
329
|
|
|
* @param string $filename The file to parse |
330
|
|
|
* @param string $uri_root The url of the file |
331
|
|
|
* @param bool $returnRoot Indicates whether the return value should be a css string a root node |
332
|
|
|
* @return Less_Tree_Ruleset|Less_Parser |
333
|
|
|
*/ |
334
|
|
|
public function parseFile( $filename, $uri_root = '', $returnRoot = false){ |
335
|
|
|
|
336
|
|
|
if( !file_exists($filename) ){ |
337
|
|
|
$this->Error(sprintf('File `%s` not found.', $filename)); |
338
|
|
|
} |
339
|
|
|
|
340
|
|
|
|
341
|
|
|
// fix uri_root? |
342
|
|
|
// Instead of The mixture of file path for the first argument and directory path for the second argument has bee |
343
|
|
|
if( !$returnRoot && !empty($uri_root) && basename($uri_root) == basename($filename) ){ |
344
|
|
|
$uri_root = dirname($uri_root); |
345
|
|
|
} |
346
|
|
|
|
347
|
|
|
|
348
|
|
|
$previousFileInfo = $this->env->currentFileInfo; |
349
|
|
|
|
350
|
|
|
|
351
|
|
|
if( $filename ){ |
352
|
|
|
$filename = self::WinPath(realpath($filename)); |
353
|
|
|
} |
354
|
|
|
$uri_root = self::WinPath($uri_root); |
355
|
|
|
|
356
|
|
|
$this->SetFileInfo($filename, $uri_root); |
357
|
|
|
|
358
|
|
|
self::AddParsedFile($filename); |
359
|
|
|
|
360
|
|
|
if( $returnRoot ){ |
361
|
|
|
$rules = $this->GetRules( $filename ); |
362
|
|
|
$return = new Less_Tree_Ruleset(array(), $rules ); |
363
|
|
|
}else{ |
364
|
|
|
$this->_parse( $filename ); |
365
|
|
|
$return = $this; |
366
|
|
|
} |
367
|
|
|
|
368
|
|
|
if( $previousFileInfo ){ |
369
|
|
|
$this->env->currentFileInfo = $previousFileInfo; |
370
|
|
|
} |
371
|
|
|
|
372
|
|
|
return $return; |
373
|
|
|
} |
374
|
|
|
|
375
|
|
|
|
376
|
|
|
/** |
377
|
|
|
* Allows a user to set variables values |
378
|
|
|
* @param array $vars |
379
|
|
|
* @return Less_Parser |
380
|
|
|
*/ |
381
|
|
|
public function ModifyVars( $vars ){ |
382
|
|
|
|
383
|
|
|
$this->input = Less_Parser::serializeVars( $vars ); |
384
|
|
|
$this->_parse(); |
385
|
|
|
|
386
|
|
|
return $this; |
387
|
|
|
} |
388
|
|
|
|
389
|
|
|
|
390
|
|
|
/** |
391
|
|
|
* @param string $filename |
392
|
|
|
*/ |
393
|
|
|
public function SetFileInfo( $filename, $uri_root = ''){ |
394
|
|
|
|
395
|
|
|
$filename = Less_Environment::normalizePath($filename); |
396
|
|
|
$dirname = preg_replace('/[^\/\\\\]*$/','',$filename); |
397
|
|
|
|
398
|
|
|
if( !empty($uri_root) ){ |
399
|
|
|
$uri_root = rtrim($uri_root,'/').'/'; |
400
|
|
|
} |
401
|
|
|
|
402
|
|
|
$currentFileInfo = array(); |
403
|
|
|
|
404
|
|
|
//entry info |
405
|
|
|
if( isset($this->env->currentFileInfo) ){ |
406
|
|
|
$currentFileInfo['entryPath'] = $this->env->currentFileInfo['entryPath']; |
407
|
|
|
$currentFileInfo['entryUri'] = $this->env->currentFileInfo['entryUri']; |
408
|
|
|
$currentFileInfo['rootpath'] = $this->env->currentFileInfo['rootpath']; |
409
|
|
|
|
410
|
|
|
}else{ |
411
|
|
|
$currentFileInfo['entryPath'] = $dirname; |
412
|
|
|
$currentFileInfo['entryUri'] = $uri_root; |
413
|
|
|
$currentFileInfo['rootpath'] = $dirname; |
414
|
|
|
} |
415
|
|
|
|
416
|
|
|
$currentFileInfo['currentDirectory'] = $dirname; |
417
|
|
|
$currentFileInfo['currentUri'] = $uri_root.basename($filename); |
418
|
|
|
$currentFileInfo['filename'] = $filename; |
419
|
|
|
$currentFileInfo['uri_root'] = $uri_root; |
420
|
|
|
|
421
|
|
|
|
422
|
|
|
//inherit reference |
423
|
|
|
if( isset($this->env->currentFileInfo['reference']) && $this->env->currentFileInfo['reference'] ){ |
424
|
|
|
$currentFileInfo['reference'] = true; |
425
|
|
|
} |
426
|
|
|
|
427
|
|
|
$this->env->currentFileInfo = $currentFileInfo; |
428
|
|
|
} |
429
|
|
|
|
430
|
|
|
|
431
|
|
|
/** |
432
|
|
|
* @deprecated 1.5.1.2 |
433
|
|
|
* |
434
|
|
|
*/ |
435
|
|
|
public function SetCacheDir( $dir ){ |
436
|
|
|
|
437
|
|
|
if( !file_exists($dir) ){ |
438
|
|
|
if( mkdir($dir) ){ |
439
|
|
|
return true; |
440
|
|
|
} |
441
|
|
|
throw new Less_Exception_Parser('Less.php cache directory couldn\'t be created: '.$dir); |
442
|
|
|
|
443
|
|
|
}elseif( !is_dir($dir) ){ |
444
|
|
|
throw new Less_Exception_Parser('Less.php cache directory doesn\'t exist: '.$dir); |
445
|
|
|
|
446
|
|
|
}elseif( !is_writable($dir) ){ |
447
|
|
|
throw new Less_Exception_Parser('Less.php cache directory isn\'t writable: '.$dir); |
448
|
|
|
|
449
|
|
|
}else{ |
450
|
|
|
$dir = self::WinPath($dir); |
451
|
|
|
Less_Cache::$cache_dir = rtrim($dir,'/').'/'; |
452
|
|
|
return true; |
453
|
|
|
} |
454
|
|
|
} |
455
|
|
|
|
456
|
|
|
|
457
|
|
|
/** |
458
|
|
|
* Set a list of directories or callbacks the parser should use for determining import paths |
459
|
|
|
* |
460
|
|
|
* @param array $dirs |
461
|
|
|
*/ |
462
|
|
|
public function SetImportDirs( $dirs ){ |
463
|
|
|
Less_Parser::$options['import_dirs'] = array(); |
464
|
|
|
|
465
|
|
|
foreach($dirs as $path => $uri_root){ |
466
|
|
|
|
467
|
|
|
$path = self::WinPath($path); |
468
|
|
|
if( !empty($path) ){ |
469
|
|
|
$path = rtrim($path,'/').'/'; |
470
|
|
|
} |
471
|
|
|
|
472
|
|
|
if ( !is_callable($uri_root) ){ |
473
|
|
|
$uri_root = self::WinPath($uri_root); |
474
|
|
|
if( !empty($uri_root) ){ |
475
|
|
|
$uri_root = rtrim($uri_root,'/').'/'; |
476
|
|
|
} |
477
|
|
|
} |
478
|
|
|
|
479
|
|
|
Less_Parser::$options['import_dirs'][$path] = $uri_root; |
480
|
|
|
} |
481
|
|
|
} |
482
|
|
|
|
483
|
|
|
/** |
484
|
|
|
* @param string $file_path |
485
|
|
|
*/ |
486
|
|
|
private function _parse( $file_path = null ){ |
487
|
|
|
$this->rules = array_merge($this->rules, $this->GetRules( $file_path )); |
488
|
|
|
} |
489
|
|
|
|
490
|
|
|
|
491
|
|
|
/** |
492
|
|
|
* Return the results of parsePrimary for $file_path |
493
|
|
|
* Use cache and save cached results if possible |
494
|
|
|
* |
495
|
|
|
* @param string|null $file_path |
496
|
|
|
*/ |
497
|
|
|
private function GetRules( $file_path ){ |
498
|
|
|
|
499
|
|
|
$this->SetInput($file_path); |
500
|
|
|
|
501
|
|
|
$cache_file = $this->CacheFile( $file_path ); |
502
|
|
|
if( $cache_file ){ |
|
|
|
|
503
|
|
|
if( Less_Parser::$options['cache_method'] == 'callback' ){ |
504
|
|
|
if( is_callable(Less_Parser::$options['cache_callback_get']) ){ |
505
|
|
|
$cache = call_user_func_array( |
506
|
|
|
Less_Parser::$options['cache_callback_get'], |
507
|
|
|
array($this, $file_path, $cache_file) |
508
|
|
|
); |
509
|
|
|
|
510
|
|
|
if( $cache ){ |
511
|
|
|
$this->UnsetInput(); |
512
|
|
|
return $cache; |
513
|
|
|
} |
514
|
|
|
} |
515
|
|
|
|
516
|
|
|
}elseif( file_exists($cache_file) ){ |
517
|
|
|
switch(Less_Parser::$options['cache_method']){ |
518
|
|
|
|
519
|
|
|
// Using serialize |
520
|
|
|
// Faster but uses more memory |
521
|
|
|
case 'serialize': |
522
|
|
|
$cache = unserialize(file_get_contents($cache_file)); |
523
|
|
|
if( $cache ){ |
524
|
|
|
touch($cache_file); |
525
|
|
|
$this->UnsetInput(); |
526
|
|
|
return $cache; |
527
|
|
|
} |
528
|
|
|
break; |
529
|
|
|
|
530
|
|
|
|
531
|
|
|
// Using generated php code |
532
|
|
|
case 'var_export': |
533
|
|
|
case 'php': |
534
|
|
|
$this->UnsetInput(); |
535
|
|
|
return include($cache_file); |
536
|
|
|
} |
537
|
|
|
} |
538
|
|
|
} |
539
|
|
|
|
540
|
|
|
$rules = $this->parsePrimary(); |
541
|
|
|
|
542
|
|
|
if( $this->pos < $this->input_len ){ |
543
|
|
|
throw new Less_Exception_Chunk($this->input, null, $this->furthest, $this->env->currentFileInfo); |
544
|
|
|
} |
545
|
|
|
|
546
|
|
|
$this->UnsetInput(); |
547
|
|
|
|
548
|
|
|
|
549
|
|
|
//save the cache |
550
|
|
|
if( $cache_file ){ |
|
|
|
|
551
|
|
|
if( Less_Parser::$options['cache_method'] == 'callback' ){ |
552
|
|
|
if( is_callable(Less_Parser::$options['cache_callback_set']) ){ |
553
|
|
|
call_user_func_array( |
554
|
|
|
Less_Parser::$options['cache_callback_set'], |
555
|
|
|
array($this, $file_path, $cache_file, $rules) |
556
|
|
|
); |
557
|
|
|
} |
558
|
|
|
|
559
|
|
|
}else{ |
560
|
|
|
//msg('write cache file'); |
561
|
|
|
switch(Less_Parser::$options['cache_method']){ |
562
|
|
|
case 'serialize': |
563
|
|
|
file_put_contents( $cache_file, serialize($rules) ); |
564
|
|
|
break; |
565
|
|
|
case 'php': |
566
|
|
|
file_put_contents( $cache_file, '<?php return '.self::ArgString($rules).'; ?>' ); |
567
|
|
|
break; |
568
|
|
|
case 'var_export': |
569
|
|
|
//Requires __set_state() |
570
|
|
|
file_put_contents( $cache_file, '<?php return '.var_export($rules,true).'; ?>' ); |
571
|
|
|
break; |
572
|
|
|
} |
573
|
|
|
|
574
|
|
|
Less_Cache::CleanCache(); |
575
|
|
|
} |
576
|
|
|
} |
577
|
|
|
|
578
|
|
|
return $rules; |
579
|
|
|
} |
580
|
|
|
|
581
|
|
|
|
582
|
|
|
/** |
583
|
|
|
* Set up the input buffer |
584
|
|
|
* |
585
|
|
|
*/ |
586
|
|
|
public function SetInput( $file_path ){ |
587
|
|
|
|
588
|
|
|
if( $file_path ){ |
589
|
|
|
$this->input = file_get_contents( $file_path ); |
590
|
|
|
} |
591
|
|
|
|
592
|
|
|
$this->pos = $this->furthest = 0; |
593
|
|
|
|
594
|
|
|
// Remove potential UTF Byte Order Mark |
595
|
|
|
$this->input = preg_replace('/\\G\xEF\xBB\xBF/', '', $this->input); |
596
|
|
|
$this->input_len = strlen($this->input); |
597
|
|
|
|
598
|
|
|
|
599
|
|
|
if( Less_Parser::$options['sourceMap'] && $this->env->currentFileInfo ){ |
|
|
|
|
600
|
|
|
$uri = $this->env->currentFileInfo['currentUri']; |
601
|
|
|
Less_Parser::$contentsMap[$uri] = $this->input; |
602
|
|
|
} |
603
|
|
|
|
604
|
|
|
} |
605
|
|
|
|
606
|
|
|
|
607
|
|
|
/** |
608
|
|
|
* Free up some memory |
609
|
|
|
* |
610
|
|
|
*/ |
611
|
|
|
public function UnsetInput(){ |
612
|
|
|
unset($this->input, $this->pos, $this->input_len, $this->furthest); |
613
|
|
|
$this->saveStack = array(); |
614
|
|
|
} |
615
|
|
|
|
616
|
|
|
|
617
|
|
|
public function CacheFile( $file_path ){ |
618
|
|
|
|
619
|
|
|
if( $file_path && $this->CacheEnabled() ){ |
620
|
|
|
|
621
|
|
|
$env = get_object_vars($this->env); |
622
|
|
|
unset($env['frames']); |
623
|
|
|
|
624
|
|
|
$parts = array(); |
625
|
|
|
$parts[] = $file_path; |
626
|
|
|
$parts[] = filesize( $file_path ); |
627
|
|
|
$parts[] = filemtime( $file_path ); |
628
|
|
|
$parts[] = $env; |
629
|
|
|
$parts[] = Less_Version::cache_version; |
630
|
|
|
$parts[] = Less_Parser::$options['cache_method']; |
631
|
|
|
return Less_Cache::$cache_dir . Less_Cache::$prefix . base_convert( sha1(json_encode($parts) ), 16, 36) . '.lesscache'; |
632
|
|
|
} |
633
|
|
|
} |
634
|
|
|
|
635
|
|
|
|
636
|
|
|
static function AddParsedFile($file){ |
|
|
|
|
637
|
|
|
self::$imports[] = $file; |
638
|
|
|
} |
639
|
|
|
|
640
|
|
|
static function AllParsedFiles(){ |
|
|
|
|
641
|
|
|
return self::$imports; |
642
|
|
|
} |
643
|
|
|
|
644
|
|
|
/** |
645
|
|
|
* @param string $file |
646
|
|
|
*/ |
647
|
|
|
static function FileParsed($file){ |
|
|
|
|
648
|
|
|
return in_array($file,self::$imports); |
649
|
|
|
} |
650
|
|
|
|
651
|
|
|
|
652
|
|
|
function save() { |
|
|
|
|
653
|
|
|
$this->saveStack[] = $this->pos; |
654
|
|
|
} |
655
|
|
|
|
656
|
|
|
private function restore() { |
657
|
|
|
$this->pos = array_pop($this->saveStack); |
658
|
|
|
} |
659
|
|
|
|
660
|
|
|
private function forget(){ |
661
|
|
|
array_pop($this->saveStack); |
662
|
|
|
} |
663
|
|
|
|
664
|
|
|
|
665
|
|
|
private function isWhitespace($offset = 0) { |
666
|
|
|
return preg_match('/\s/',$this->input[ $this->pos + $offset]); |
667
|
|
|
} |
668
|
|
|
|
669
|
|
|
/** |
670
|
|
|
* Parse from a token, regexp or string, and move forward if match |
671
|
|
|
* |
672
|
|
|
* @param array $toks |
673
|
|
|
* @return array |
674
|
|
|
*/ |
675
|
|
|
private function match($toks){ |
676
|
|
|
|
677
|
|
|
// The match is confirmed, add the match length to `this::pos`, |
678
|
|
|
// and consume any extra white-space characters (' ' || '\n') |
679
|
|
|
// which come after that. The reason for this is that LeSS's |
680
|
|
|
// grammar is mostly white-space insensitive. |
681
|
|
|
// |
682
|
|
|
|
683
|
|
|
foreach($toks as $tok){ |
684
|
|
|
|
685
|
|
|
$char = $tok[0]; |
686
|
|
|
|
687
|
|
|
if( $char === '/' ){ |
688
|
|
|
$match = $this->MatchReg($tok); |
689
|
|
|
|
690
|
|
|
if( $match ){ |
691
|
|
|
return count($match) === 1 ? $match[0] : $match; |
692
|
|
|
} |
693
|
|
|
|
694
|
|
|
}elseif( $char === '#' ){ |
695
|
|
|
$match = $this->MatchChar($tok[1]); |
696
|
|
|
|
697
|
|
|
}else{ |
698
|
|
|
// Non-terminal, match using a function call |
699
|
|
|
$match = $this->$tok(); |
700
|
|
|
|
701
|
|
|
} |
702
|
|
|
|
703
|
|
|
if( $match ){ |
704
|
|
|
return $match; |
705
|
|
|
} |
706
|
|
|
} |
707
|
|
|
} |
708
|
|
|
|
709
|
|
|
/** |
710
|
|
|
* @param string[] $toks |
711
|
|
|
* |
712
|
|
|
* @return string |
713
|
|
|
*/ |
714
|
|
|
private function MatchFuncs($toks){ |
715
|
|
|
|
716
|
|
|
if( $this->pos < $this->input_len ){ |
717
|
|
|
foreach($toks as $tok){ |
718
|
|
|
$match = $this->$tok(); |
719
|
|
|
if( $match ){ |
720
|
|
|
return $match; |
721
|
|
|
} |
722
|
|
|
} |
723
|
|
|
} |
724
|
|
|
|
725
|
|
|
} |
726
|
|
|
|
727
|
|
|
// Match a single character in the input, |
728
|
|
|
private function MatchChar($tok){ |
729
|
|
|
if( ($this->pos < $this->input_len) && ($this->input[$this->pos] === $tok) ){ |
730
|
|
|
$this->skipWhitespace(1); |
731
|
|
|
return $tok; |
732
|
|
|
} |
733
|
|
|
} |
734
|
|
|
|
735
|
|
|
// Match a regexp from the current start point |
736
|
|
|
private function MatchReg($tok){ |
737
|
|
|
|
738
|
|
|
if( preg_match($tok, $this->input, $match, 0, $this->pos) ){ |
739
|
|
|
$this->skipWhitespace(strlen($match[0])); |
740
|
|
|
return $match; |
741
|
|
|
} |
742
|
|
|
} |
743
|
|
|
|
744
|
|
|
|
745
|
|
|
/** |
746
|
|
|
* Same as match(), but don't change the state of the parser, |
747
|
|
|
* just return the match. |
748
|
|
|
* |
749
|
|
|
* @param string $tok |
750
|
|
|
* @return integer |
751
|
|
|
*/ |
752
|
|
|
public function PeekReg($tok){ |
753
|
|
|
return preg_match($tok, $this->input, $match, 0, $this->pos); |
754
|
|
|
} |
755
|
|
|
|
756
|
|
|
/** |
757
|
|
|
* @param string $tok |
758
|
|
|
*/ |
759
|
|
|
public function PeekChar($tok){ |
760
|
|
|
//return ($this->input[$this->pos] === $tok ); |
761
|
|
|
return ($this->pos < $this->input_len) && ($this->input[$this->pos] === $tok ); |
762
|
|
|
} |
763
|
|
|
|
764
|
|
|
|
765
|
|
|
/** |
766
|
|
|
* @param integer $length |
767
|
|
|
*/ |
768
|
|
|
public function skipWhitespace($length){ |
769
|
|
|
|
770
|
|
|
$this->pos += $length; |
771
|
|
|
|
772
|
|
|
for(; $this->pos < $this->input_len; $this->pos++ ){ |
773
|
|
|
$c = $this->input[$this->pos]; |
774
|
|
|
|
775
|
|
|
if( ($c !== "\n") && ($c !== "\r") && ($c !== "\t") && ($c !== ' ') ){ |
776
|
|
|
break; |
777
|
|
|
} |
778
|
|
|
} |
779
|
|
|
} |
780
|
|
|
|
781
|
|
|
|
782
|
|
|
/** |
783
|
|
|
* @param string $tok |
784
|
|
|
* @param string|null $msg |
785
|
|
|
*/ |
786
|
|
View Code Duplication |
public function expect($tok, $msg = NULL) { |
787
|
|
|
$result = $this->match( array($tok) ); |
788
|
|
|
if (!$result) { |
|
|
|
|
789
|
|
|
$this->Error( $msg ? "Expected '" . $tok . "' got '" . $this->input[$this->pos] . "'" : $msg ); |
790
|
|
|
} else { |
791
|
|
|
return $result; |
792
|
|
|
} |
793
|
|
|
} |
794
|
|
|
|
795
|
|
|
/** |
796
|
|
|
* @param string $tok |
797
|
|
|
*/ |
798
|
|
View Code Duplication |
public function expectChar($tok, $msg = null ){ |
799
|
|
|
$result = $this->MatchChar($tok); |
800
|
|
|
if( !$result ){ |
801
|
|
|
$this->Error( $msg ? "Expected '" . $tok . "' got '" . $this->input[$this->pos] . "'" : $msg ); |
802
|
|
|
}else{ |
803
|
|
|
return $result; |
804
|
|
|
} |
805
|
|
|
} |
806
|
|
|
|
807
|
|
|
// |
808
|
|
|
// Here in, the parsing rules/functions |
809
|
|
|
// |
810
|
|
|
// The basic structure of the syntax tree generated is as follows: |
811
|
|
|
// |
812
|
|
|
// Ruleset -> Rule -> Value -> Expression -> Entity |
813
|
|
|
// |
814
|
|
|
// Here's some LESS code: |
815
|
|
|
// |
816
|
|
|
// .class { |
817
|
|
|
// color: #fff; |
818
|
|
|
// border: 1px solid #000; |
819
|
|
|
// width: @w + 4px; |
820
|
|
|
// > .child {...} |
821
|
|
|
// } |
822
|
|
|
// |
823
|
|
|
// And here's what the parse tree might look like: |
824
|
|
|
// |
825
|
|
|
// Ruleset (Selector '.class', [ |
826
|
|
|
// Rule ("color", Value ([Expression [Color #fff]])) |
827
|
|
|
// Rule ("border", Value ([Expression [Dimension 1px][Keyword "solid"][Color #000]])) |
828
|
|
|
// Rule ("width", Value ([Expression [Operation "+" [Variable "@w"][Dimension 4px]]])) |
829
|
|
|
// Ruleset (Selector [Element '>', '.child'], [...]) |
830
|
|
|
// ]) |
831
|
|
|
// |
832
|
|
|
// In general, most rules will try to parse a token with the `$()` function, and if the return |
833
|
|
|
// value is truly, will return a new node, of the relevant type. Sometimes, we need to check |
834
|
|
|
// first, before parsing, that's when we use `peek()`. |
835
|
|
|
// |
836
|
|
|
|
837
|
|
|
// |
838
|
|
|
// The `primary` rule is the *entry* and *exit* point of the parser. |
839
|
|
|
// The rules here can appear at any level of the parse tree. |
840
|
|
|
// |
841
|
|
|
// The recursive nature of the grammar is an interplay between the `block` |
842
|
|
|
// rule, which represents `{ ... }`, the `ruleset` rule, and this `primary` rule, |
843
|
|
|
// as represented by this simplified grammar: |
844
|
|
|
// |
845
|
|
|
// primary → (ruleset | rule)+ |
846
|
|
|
// ruleset → selector+ block |
847
|
|
|
// block → '{' primary '}' |
848
|
|
|
// |
849
|
|
|
// Only at one point is the primary rule not called from the |
850
|
|
|
// block rule: at the root level. |
851
|
|
|
// |
852
|
|
|
private function parsePrimary(){ |
853
|
|
|
$root = array(); |
854
|
|
|
|
855
|
|
|
while( true ){ |
856
|
|
|
|
857
|
|
|
if( $this->pos >= $this->input_len ){ |
858
|
|
|
break; |
859
|
|
|
} |
860
|
|
|
|
861
|
|
|
$node = $this->parseExtend(true); |
862
|
|
|
if( $node ){ |
|
|
|
|
863
|
|
|
$root = array_merge($root,$node); |
864
|
|
|
continue; |
865
|
|
|
} |
866
|
|
|
|
867
|
|
|
//$node = $this->MatchFuncs( array( 'parseMixinDefinition', 'parseRule', 'parseRuleset', 'parseMixinCall', 'parseComment', 'parseDirective')); |
868
|
|
|
$node = $this->MatchFuncs( array( 'parseMixinDefinition', 'parseNameValue', 'parseRule', 'parseRuleset', 'parseMixinCall', 'parseComment', 'parseRulesetCall', 'parseDirective')); |
869
|
|
|
|
870
|
|
|
if( $node ){ |
871
|
|
|
$root[] = $node; |
872
|
|
|
}elseif( !$this->MatchReg('/\\G[\s\n;]+/') ){ |
873
|
|
|
break; |
874
|
|
|
} |
875
|
|
|
|
876
|
|
|
if( $this->PeekChar('}') ){ |
877
|
|
|
break; |
878
|
|
|
} |
879
|
|
|
} |
880
|
|
|
|
881
|
|
|
return $root; |
882
|
|
|
} |
883
|
|
|
|
884
|
|
|
|
885
|
|
|
|
886
|
|
|
// We create a Comment node for CSS comments `/* */`, |
887
|
|
|
// but keep the LeSS comments `//` silent, by just skipping |
888
|
|
|
// over them. |
889
|
|
|
private function parseComment(){ |
890
|
|
|
|
891
|
|
|
if( $this->input[$this->pos] !== '/' ){ |
892
|
|
|
return; |
893
|
|
|
} |
894
|
|
|
|
895
|
|
|
if( $this->input[$this->pos+1] === '/' ){ |
896
|
|
|
$match = $this->MatchReg('/\\G\/\/.*/'); |
897
|
|
|
return $this->NewObj4('Less_Tree_Comment',array($match[0], true, $this->pos, $this->env->currentFileInfo)); |
898
|
|
|
} |
899
|
|
|
|
900
|
|
|
//$comment = $this->MatchReg('/\\G\/\*(?:[^*]|\*+[^\/*])*\*+\/\n?/'); |
901
|
|
|
$comment = $this->MatchReg('/\\G\/\*(?s).*?\*+\/\n?/');//not the same as less.js to prevent fatal errors |
902
|
|
|
if( $comment ){ |
903
|
|
|
return $this->NewObj4('Less_Tree_Comment',array($comment[0], false, $this->pos, $this->env->currentFileInfo)); |
904
|
|
|
} |
905
|
|
|
} |
906
|
|
|
|
907
|
|
|
private function parseComments(){ |
908
|
|
|
$comments = array(); |
909
|
|
|
|
910
|
|
|
while( $this->pos < $this->input_len ){ |
911
|
|
|
$comment = $this->parseComment(); |
912
|
|
|
if( !$comment ){ |
913
|
|
|
break; |
914
|
|
|
} |
915
|
|
|
|
916
|
|
|
$comments[] = $comment; |
917
|
|
|
} |
918
|
|
|
|
919
|
|
|
return $comments; |
920
|
|
|
} |
921
|
|
|
|
922
|
|
|
|
923
|
|
|
|
924
|
|
|
// |
925
|
|
|
// A string, which supports escaping " and ' |
926
|
|
|
// |
927
|
|
|
// "milky way" 'he\'s the one!' |
928
|
|
|
// |
929
|
|
|
private function parseEntitiesQuoted() { |
930
|
|
|
$j = $this->pos; |
931
|
|
|
$e = false; |
932
|
|
|
$index = $this->pos; |
933
|
|
|
|
934
|
|
|
if( $this->input[$this->pos] === '~' ){ |
935
|
|
|
$j++; |
936
|
|
|
$e = true; // Escaped strings |
937
|
|
|
} |
938
|
|
|
|
939
|
|
|
if( $this->input[$j] != '"' && $this->input[$j] !== "'" ){ |
940
|
|
|
return; |
941
|
|
|
} |
942
|
|
|
|
943
|
|
|
if ($e) { |
944
|
|
|
$this->MatchChar('~'); |
945
|
|
|
} |
946
|
|
|
|
947
|
|
|
// Fix for #124: match escaped newlines |
948
|
|
|
//$str = $this->MatchReg('/\\G"((?:[^"\\\\\r\n]|\\\\.)*)"|\'((?:[^\'\\\\\r\n]|\\\\.)*)\'/'); |
949
|
|
|
$str = $this->MatchReg('/\\G"((?:[^"\\\\\r\n]|\\\\.|\\\\\r\n|\\\\[\n\r\f])*)"|\'((?:[^\'\\\\\r\n]|\\\\.|\\\\\r\n|\\\\[\n\r\f])*)\'/'); |
950
|
|
|
|
951
|
|
|
if( $str ){ |
952
|
|
|
$result = $str[0][0] == '"' ? $str[1] : $str[2]; |
953
|
|
|
return $this->NewObj5('Less_Tree_Quoted',array($str[0], $result, $e, $index, $this->env->currentFileInfo) ); |
954
|
|
|
} |
955
|
|
|
return; |
956
|
|
|
} |
957
|
|
|
|
958
|
|
|
|
959
|
|
|
// |
960
|
|
|
// A catch-all word, such as: |
961
|
|
|
// |
962
|
|
|
// black border-collapse |
963
|
|
|
// |
964
|
|
|
private function parseEntitiesKeyword(){ |
965
|
|
|
|
966
|
|
|
//$k = $this->MatchReg('/\\G[_A-Za-z-][_A-Za-z0-9-]*/'); |
967
|
|
|
$k = $this->MatchReg('/\\G%|\\G[_A-Za-z-][_A-Za-z0-9-]*/'); |
968
|
|
|
if( $k ){ |
969
|
|
|
$k = $k[0]; |
970
|
|
|
$color = $this->fromKeyword($k); |
971
|
|
|
if( $color ){ |
972
|
|
|
return $color; |
973
|
|
|
} |
974
|
|
|
return $this->NewObj1('Less_Tree_Keyword',$k); |
975
|
|
|
} |
976
|
|
|
} |
977
|
|
|
|
978
|
|
|
// duplicate of Less_Tree_Color::FromKeyword |
979
|
|
|
private function FromKeyword( $keyword ){ |
980
|
|
|
$keyword = strtolower($keyword); |
981
|
|
|
|
982
|
|
|
if( Less_Colors::hasOwnProperty($keyword) ){ |
983
|
|
|
// detect named color |
984
|
|
|
return $this->NewObj1('Less_Tree_Color',substr(Less_Colors::color($keyword), 1)); |
985
|
|
|
} |
986
|
|
|
|
987
|
|
|
if( $keyword === 'transparent' ){ |
988
|
|
|
return $this->NewObj3('Less_Tree_Color', array( array(0, 0, 0), 0, true)); |
989
|
|
|
} |
990
|
|
|
} |
991
|
|
|
|
992
|
|
|
// |
993
|
|
|
// A function call |
994
|
|
|
// |
995
|
|
|
// rgb(255, 0, 255) |
996
|
|
|
// |
997
|
|
|
// We also try to catch IE's `alpha()`, but let the `alpha` parser |
998
|
|
|
// deal with the details. |
999
|
|
|
// |
1000
|
|
|
// The arguments are parsed with the `entities.arguments` parser. |
1001
|
|
|
// |
1002
|
|
|
private function parseEntitiesCall(){ |
1003
|
|
|
$index = $this->pos; |
1004
|
|
|
|
1005
|
|
|
if( !preg_match('/\\G([\w-]+|%|progid:[\w\.]+)\(/', $this->input, $name,0,$this->pos) ){ |
1006
|
|
|
return; |
1007
|
|
|
} |
1008
|
|
|
$name = $name[1]; |
1009
|
|
|
$nameLC = strtolower($name); |
1010
|
|
|
|
1011
|
|
|
if ($nameLC === 'url') { |
1012
|
|
|
return null; |
1013
|
|
|
} |
1014
|
|
|
|
1015
|
|
|
$this->pos += strlen($name); |
1016
|
|
|
|
1017
|
|
|
if( $nameLC === 'alpha' ){ |
1018
|
|
|
$alpha_ret = $this->parseAlpha(); |
1019
|
|
|
if( $alpha_ret ){ |
1020
|
|
|
return $alpha_ret; |
1021
|
|
|
} |
1022
|
|
|
} |
1023
|
|
|
|
1024
|
|
|
$this->MatchChar('('); // Parse the '(' and consume whitespace. |
1025
|
|
|
|
1026
|
|
|
$args = $this->parseEntitiesArguments(); |
1027
|
|
|
|
1028
|
|
|
if( !$this->MatchChar(')') ){ |
1029
|
|
|
return; |
1030
|
|
|
} |
1031
|
|
|
|
1032
|
|
|
if ($name) { |
1033
|
|
|
return $this->NewObj4('Less_Tree_Call',array($name, $args, $index, $this->env->currentFileInfo) ); |
1034
|
|
|
} |
1035
|
|
|
} |
1036
|
|
|
|
1037
|
|
|
/** |
1038
|
|
|
* Parse a list of arguments |
1039
|
|
|
* |
1040
|
|
|
* @return array |
1041
|
|
|
*/ |
1042
|
|
|
private function parseEntitiesArguments(){ |
1043
|
|
|
|
1044
|
|
|
$args = array(); |
1045
|
|
|
while( true ){ |
1046
|
|
|
$arg = $this->MatchFuncs( array('parseEntitiesAssignment','parseExpression') ); |
1047
|
|
|
if( !$arg ){ |
1048
|
|
|
break; |
1049
|
|
|
} |
1050
|
|
|
|
1051
|
|
|
$args[] = $arg; |
1052
|
|
|
if( !$this->MatchChar(',') ){ |
1053
|
|
|
break; |
1054
|
|
|
} |
1055
|
|
|
} |
1056
|
|
|
return $args; |
1057
|
|
|
} |
1058
|
|
|
|
1059
|
|
|
private function parseEntitiesLiteral(){ |
1060
|
|
|
return $this->MatchFuncs( array('parseEntitiesDimension','parseEntitiesColor','parseEntitiesQuoted','parseUnicodeDescriptor') ); |
1061
|
|
|
} |
1062
|
|
|
|
1063
|
|
|
// Assignments are argument entities for calls. |
1064
|
|
|
// They are present in ie filter properties as shown below. |
1065
|
|
|
// |
1066
|
|
|
// filter: progid:DXImageTransform.Microsoft.Alpha( *opacity=50* ) |
1067
|
|
|
// |
1068
|
|
|
private function parseEntitiesAssignment() { |
1069
|
|
|
|
1070
|
|
|
$key = $this->MatchReg('/\\G\w+(?=\s?=)/'); |
1071
|
|
|
if( !$key ){ |
1072
|
|
|
return; |
1073
|
|
|
} |
1074
|
|
|
|
1075
|
|
|
if( !$this->MatchChar('=') ){ |
1076
|
|
|
return; |
1077
|
|
|
} |
1078
|
|
|
|
1079
|
|
|
$value = $this->parseEntity(); |
1080
|
|
|
if( $value ){ |
1081
|
|
|
return $this->NewObj2('Less_Tree_Assignment',array($key[0], $value)); |
1082
|
|
|
} |
1083
|
|
|
} |
1084
|
|
|
|
1085
|
|
|
// |
1086
|
|
|
// Parse url() tokens |
1087
|
|
|
// |
1088
|
|
|
// We use a specific rule for urls, because they don't really behave like |
1089
|
|
|
// standard function calls. The difference is that the argument doesn't have |
1090
|
|
|
// to be enclosed within a string, so it can't be parsed as an Expression. |
1091
|
|
|
// |
1092
|
|
|
private function parseEntitiesUrl(){ |
1093
|
|
|
|
1094
|
|
|
|
1095
|
|
|
if( $this->input[$this->pos] !== 'u' || !$this->matchReg('/\\Gurl\(/') ){ |
1096
|
|
|
return; |
1097
|
|
|
} |
1098
|
|
|
|
1099
|
|
|
$value = $this->match( array('parseEntitiesQuoted','parseEntitiesVariable','/\\Gdata\:.*?[^\)]+/','/\\G(?:(?:\\\\[\(\)\'"])|[^\(\)\'"])+/') ); |
1100
|
|
|
if( !$value ){ |
|
|
|
|
1101
|
|
|
$value = ''; |
1102
|
|
|
} |
1103
|
|
|
|
1104
|
|
|
|
1105
|
|
|
$this->expectChar(')'); |
1106
|
|
|
|
1107
|
|
|
|
1108
|
|
|
if( isset($value->value) || $value instanceof Less_Tree_Variable ){ |
1109
|
|
|
return $this->NewObj2('Less_Tree_Url',array($value, $this->env->currentFileInfo)); |
1110
|
|
|
} |
1111
|
|
|
|
1112
|
|
|
return $this->NewObj2('Less_Tree_Url', array( $this->NewObj1('Less_Tree_Anonymous',$value), $this->env->currentFileInfo) ); |
1113
|
|
|
} |
1114
|
|
|
|
1115
|
|
|
|
1116
|
|
|
// |
1117
|
|
|
// A Variable entity, such as `@fink`, in |
1118
|
|
|
// |
1119
|
|
|
// width: @fink + 2px |
1120
|
|
|
// |
1121
|
|
|
// We use a different parser for variable definitions, |
1122
|
|
|
// see `parsers.variable`. |
1123
|
|
|
// |
1124
|
|
|
private function parseEntitiesVariable(){ |
1125
|
|
|
$index = $this->pos; |
1126
|
|
|
if ($this->PeekChar('@') && ($name = $this->MatchReg('/\\G@@?[\w-]+/'))) { |
1127
|
|
|
return $this->NewObj3('Less_Tree_Variable', array( $name[0], $index, $this->env->currentFileInfo)); |
1128
|
|
|
} |
1129
|
|
|
} |
1130
|
|
|
|
1131
|
|
|
|
1132
|
|
|
// A variable entity using the protective {} e.g. @{var} |
1133
|
|
|
private function parseEntitiesVariableCurly() { |
1134
|
|
|
$index = $this->pos; |
1135
|
|
|
|
1136
|
|
|
if( $this->input_len > ($this->pos+1) && $this->input[$this->pos] === '@' && ($curly = $this->MatchReg('/\\G@\{([\w-]+)\}/')) ){ |
1137
|
|
|
return $this->NewObj3('Less_Tree_Variable',array('@'.$curly[1], $index, $this->env->currentFileInfo)); |
1138
|
|
|
} |
1139
|
|
|
} |
1140
|
|
|
|
1141
|
|
|
// |
1142
|
|
|
// A Hexadecimal color |
1143
|
|
|
// |
1144
|
|
|
// #4F3C2F |
1145
|
|
|
// |
1146
|
|
|
// `rgb` and `hsl` colors are parsed through the `entities.call` parser. |
1147
|
|
|
// |
1148
|
|
|
private function parseEntitiesColor(){ |
1149
|
|
|
if ($this->PeekChar('#') && ($rgb = $this->MatchReg('/\\G#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})/'))) { |
1150
|
|
|
return $this->NewObj1('Less_Tree_Color',$rgb[1]); |
1151
|
|
|
} |
1152
|
|
|
} |
1153
|
|
|
|
1154
|
|
|
// |
1155
|
|
|
// A Dimension, that is, a number and a unit |
1156
|
|
|
// |
1157
|
|
|
// 0.5em 95% |
1158
|
|
|
// |
1159
|
|
|
private function parseEntitiesDimension(){ |
1160
|
|
|
|
1161
|
|
|
$c = @ord($this->input[$this->pos]); |
1162
|
|
|
|
1163
|
|
|
//Is the first char of the dimension 0-9, '.', '+' or '-' |
1164
|
|
|
if (($c > 57 || $c < 43) || $c === 47 || $c == 44){ |
1165
|
|
|
return; |
1166
|
|
|
} |
1167
|
|
|
|
1168
|
|
|
$value = $this->MatchReg('/\\G([+-]?\d*\.?\d+)(%|[a-z]+)?/'); |
1169
|
|
|
if( $value ){ |
1170
|
|
|
|
1171
|
|
|
if( isset($value[2]) ){ |
1172
|
|
|
return $this->NewObj2('Less_Tree_Dimension', array($value[1],$value[2])); |
1173
|
|
|
} |
1174
|
|
|
return $this->NewObj1('Less_Tree_Dimension',$value[1]); |
1175
|
|
|
} |
1176
|
|
|
} |
1177
|
|
|
|
1178
|
|
|
|
1179
|
|
|
// |
1180
|
|
|
// A unicode descriptor, as is used in unicode-range |
1181
|
|
|
// |
1182
|
|
|
// U+0?? or U+00A1-00A9 |
1183
|
|
|
// |
1184
|
|
|
function parseUnicodeDescriptor() { |
|
|
|
|
1185
|
|
|
$ud = $this->MatchReg('/\\G(U\+[0-9a-fA-F?]+)(\-[0-9a-fA-F?]+)?/'); |
1186
|
|
|
if( $ud ){ |
1187
|
|
|
return $this->NewObj1('Less_Tree_UnicodeDescriptor', $ud[0]); |
1188
|
|
|
} |
1189
|
|
|
} |
1190
|
|
|
|
1191
|
|
|
|
1192
|
|
|
// |
1193
|
|
|
// JavaScript code to be evaluated |
1194
|
|
|
// |
1195
|
|
|
// `window.location.href` |
1196
|
|
|
// |
1197
|
|
|
private function parseEntitiesJavascript(){ |
1198
|
|
|
$e = false; |
1199
|
|
|
$j = $this->pos; |
1200
|
|
|
if( $this->input[$j] === '~' ){ |
1201
|
|
|
$j++; |
1202
|
|
|
$e = true; |
1203
|
|
|
} |
1204
|
|
|
if( $this->input[$j] !== '`' ){ |
1205
|
|
|
return; |
1206
|
|
|
} |
1207
|
|
|
if( $e ){ |
1208
|
|
|
$this->MatchChar('~'); |
1209
|
|
|
} |
1210
|
|
|
$str = $this->MatchReg('/\\G`([^`]*)`/'); |
1211
|
|
|
if( $str ){ |
1212
|
|
|
return $this->NewObj3('Less_Tree_Javascript', array($str[1], $this->pos, $e)); |
1213
|
|
|
} |
1214
|
|
|
} |
1215
|
|
|
|
1216
|
|
|
|
1217
|
|
|
// |
1218
|
|
|
// The variable part of a variable definition. Used in the `rule` parser |
1219
|
|
|
// |
1220
|
|
|
// @fink: |
1221
|
|
|
// |
1222
|
|
|
private function parseVariable(){ |
1223
|
|
|
if ($this->PeekChar('@') && ($name = $this->MatchReg('/\\G(@[\w-]+)\s*:/'))) { |
1224
|
|
|
return $name[1]; |
1225
|
|
|
} |
1226
|
|
|
} |
1227
|
|
|
|
1228
|
|
|
|
1229
|
|
|
// |
1230
|
|
|
// The variable part of a variable definition. Used in the `rule` parser |
1231
|
|
|
// |
1232
|
|
|
// @fink(); |
1233
|
|
|
// |
1234
|
|
|
private function parseRulesetCall(){ |
1235
|
|
|
|
1236
|
|
|
if( $this->input[$this->pos] === '@' && ($name = $this->MatchReg('/\\G(@[\w-]+)\s*\(\s*\)\s*;/')) ){ |
1237
|
|
|
return $this->NewObj1('Less_Tree_RulesetCall', $name[1] ); |
1238
|
|
|
} |
1239
|
|
|
} |
1240
|
|
|
|
1241
|
|
|
|
1242
|
|
|
// |
1243
|
|
|
// extend syntax - used to extend selectors |
1244
|
|
|
// |
1245
|
|
|
function parseExtend($isRule = false){ |
|
|
|
|
1246
|
|
|
|
1247
|
|
|
$index = $this->pos; |
1248
|
|
|
$extendList = array(); |
1249
|
|
|
|
1250
|
|
|
|
1251
|
|
|
if( !$this->MatchReg( $isRule ? '/\\G&:extend\(/' : '/\\G:extend\(/' ) ){ return; } |
1252
|
|
|
|
1253
|
|
|
do{ |
1254
|
|
|
$option = null; |
1255
|
|
|
$elements = array(); |
1256
|
|
|
while( true ){ |
1257
|
|
|
$option = $this->MatchReg('/\\G(all)(?=\s*(\)|,))/'); |
1258
|
|
|
if( $option ){ break; } |
|
|
|
|
1259
|
|
|
$e = $this->parseElement(); |
1260
|
|
|
if( !$e ){ break; } |
1261
|
|
|
$elements[] = $e; |
1262
|
|
|
} |
1263
|
|
|
|
1264
|
|
|
if( $option ){ |
1265
|
|
|
$option = $option[1]; |
1266
|
|
|
} |
1267
|
|
|
|
1268
|
|
|
$extendList[] = $this->NewObj3('Less_Tree_Extend', array( $this->NewObj1('Less_Tree_Selector',$elements), $option, $index )); |
1269
|
|
|
|
1270
|
|
|
}while( $this->MatchChar(",") ); |
1271
|
|
|
|
1272
|
|
|
$this->expect('/\\G\)/'); |
1273
|
|
|
|
1274
|
|
|
if( $isRule ){ |
1275
|
|
|
$this->expect('/\\G;/'); |
1276
|
|
|
} |
1277
|
|
|
|
1278
|
|
|
return $extendList; |
1279
|
|
|
} |
1280
|
|
|
|
1281
|
|
|
|
1282
|
|
|
// |
1283
|
|
|
// A Mixin call, with an optional argument list |
1284
|
|
|
// |
1285
|
|
|
// #mixins > .square(#fff); |
1286
|
|
|
// .rounded(4px, black); |
1287
|
|
|
// .button; |
1288
|
|
|
// |
1289
|
|
|
// The `while` loop is there because mixins can be |
1290
|
|
|
// namespaced, but we only support the child and descendant |
1291
|
|
|
// selector for now. |
1292
|
|
|
// |
1293
|
|
|
private function parseMixinCall(){ |
1294
|
|
|
|
1295
|
|
|
$char = $this->input[$this->pos]; |
1296
|
|
|
if( $char !== '.' && $char !== '#' ){ |
1297
|
|
|
return; |
1298
|
|
|
} |
1299
|
|
|
|
1300
|
|
|
$index = $this->pos; |
1301
|
|
|
$this->save(); // stop us absorbing part of an invalid selector |
1302
|
|
|
|
1303
|
|
|
$elements = $this->parseMixinCallElements(); |
1304
|
|
|
|
1305
|
|
|
if( $elements ){ |
|
|
|
|
1306
|
|
|
|
1307
|
|
|
if( $this->MatchChar('(') ){ |
1308
|
|
|
$returned = $this->parseMixinArgs(true); |
1309
|
|
|
$args = $returned['args']; |
1310
|
|
|
$this->expectChar(')'); |
1311
|
|
|
}else{ |
1312
|
|
|
$args = array(); |
1313
|
|
|
} |
1314
|
|
|
|
1315
|
|
|
$important = $this->parseImportant(); |
1316
|
|
|
|
1317
|
|
|
if( $this->parseEnd() ){ |
1318
|
|
|
$this->forget(); |
1319
|
|
|
return $this->NewObj5('Less_Tree_Mixin_Call', array( $elements, $args, $index, $this->env->currentFileInfo, $important)); |
1320
|
|
|
} |
1321
|
|
|
} |
1322
|
|
|
|
1323
|
|
|
$this->restore(); |
1324
|
|
|
} |
1325
|
|
|
|
1326
|
|
|
|
1327
|
|
|
private function parseMixinCallElements(){ |
1328
|
|
|
$elements = array(); |
1329
|
|
|
$c = null; |
1330
|
|
|
|
1331
|
|
|
while( true ){ |
1332
|
|
|
$elemIndex = $this->pos; |
1333
|
|
|
$e = $this->MatchReg('/\\G[#.](?:[\w-]|\\\\(?:[A-Fa-f0-9]{1,6} ?|[^A-Fa-f0-9]))+/'); |
1334
|
|
|
if( !$e ){ |
|
|
|
|
1335
|
|
|
break; |
1336
|
|
|
} |
1337
|
|
|
$elements[] = $this->NewObj4('Less_Tree_Element', array($c, $e[0], $elemIndex, $this->env->currentFileInfo)); |
1338
|
|
|
$c = $this->MatchChar('>'); |
1339
|
|
|
} |
1340
|
|
|
|
1341
|
|
|
return $elements; |
1342
|
|
|
} |
1343
|
|
|
|
1344
|
|
|
|
1345
|
|
|
|
1346
|
|
|
/** |
1347
|
|
|
* @param boolean $isCall |
1348
|
|
|
*/ |
1349
|
|
|
private function parseMixinArgs( $isCall ){ |
1350
|
|
|
$expressions = array(); |
1351
|
|
|
$argsSemiColon = array(); |
1352
|
|
|
$isSemiColonSeperated = null; |
1353
|
|
|
$argsComma = array(); |
1354
|
|
|
$expressionContainsNamed = null; |
1355
|
|
|
$name = null; |
1356
|
|
|
$returner = array('args'=>array(), 'variadic'=> false); |
1357
|
|
|
|
1358
|
|
|
$this->save(); |
1359
|
|
|
|
1360
|
|
|
while( true ){ |
1361
|
|
|
if( $isCall ){ |
1362
|
|
|
$arg = $this->MatchFuncs( array( 'parseDetachedRuleset','parseExpression' ) ); |
1363
|
|
|
} else { |
1364
|
|
|
$this->parseComments(); |
1365
|
|
|
if( $this->input[ $this->pos ] === '.' && $this->MatchReg('/\\G\.{3}/') ){ |
1366
|
|
|
$returner['variadic'] = true; |
1367
|
|
|
if( $this->MatchChar(";") && !$isSemiColonSeperated ){ |
|
|
|
|
1368
|
|
|
$isSemiColonSeperated = true; |
1369
|
|
|
} |
1370
|
|
|
|
1371
|
|
|
if( $isSemiColonSeperated ){ |
1372
|
|
|
$argsSemiColon[] = array('variadic'=>true); |
1373
|
|
|
}else{ |
1374
|
|
|
$argsComma[] = array('variadic'=>true); |
1375
|
|
|
} |
1376
|
|
|
break; |
1377
|
|
|
} |
1378
|
|
|
$arg = $this->MatchFuncs( array('parseEntitiesVariable','parseEntitiesLiteral','parseEntitiesKeyword') ); |
1379
|
|
|
} |
1380
|
|
|
|
1381
|
|
|
if( !$arg ){ |
1382
|
|
|
break; |
1383
|
|
|
} |
1384
|
|
|
|
1385
|
|
|
|
1386
|
|
|
$nameLoop = null; |
1387
|
|
|
if( $arg instanceof Less_Tree_Expression ){ |
1388
|
|
|
$arg->throwAwayComments(); |
1389
|
|
|
} |
1390
|
|
|
$value = $arg; |
1391
|
|
|
$val = null; |
1392
|
|
|
|
1393
|
|
|
if( $isCall ){ |
1394
|
|
|
// Variable |
1395
|
|
|
if( property_exists($arg,'value') && count($arg->value) == 1 ){ |
1396
|
|
|
$val = $arg->value[0]; |
1397
|
|
|
} |
1398
|
|
|
} else { |
1399
|
|
|
$val = $arg; |
1400
|
|
|
} |
1401
|
|
|
|
1402
|
|
|
|
1403
|
|
|
if( $val instanceof Less_Tree_Variable ){ |
1404
|
|
|
|
1405
|
|
|
if( $this->MatchChar(':') ){ |
1406
|
|
|
if( $expressions ){ |
|
|
|
|
1407
|
|
|
if( $isSemiColonSeperated ){ |
1408
|
|
|
$this->Error('Cannot mix ; and , as delimiter types'); |
1409
|
|
|
} |
1410
|
|
|
$expressionContainsNamed = true; |
1411
|
|
|
} |
1412
|
|
|
|
1413
|
|
|
// we do not support setting a ruleset as a default variable - it doesn't make sense |
1414
|
|
|
// However if we do want to add it, there is nothing blocking it, just don't error |
1415
|
|
|
// and remove isCall dependency below |
1416
|
|
|
$value = null; |
1417
|
|
|
if( $isCall ){ |
1418
|
|
|
$value = $this->parseDetachedRuleset(); |
1419
|
|
|
} |
1420
|
|
|
if( !$value ){ |
1421
|
|
|
$value = $this->parseExpression(); |
1422
|
|
|
} |
1423
|
|
|
|
1424
|
|
|
if( !$value ){ |
1425
|
|
|
if( $isCall ){ |
1426
|
|
|
$this->Error('could not understand value for named argument'); |
1427
|
|
|
} else { |
1428
|
|
|
$this->restore(); |
1429
|
|
|
$returner['args'] = array(); |
1430
|
|
|
return $returner; |
1431
|
|
|
} |
1432
|
|
|
} |
1433
|
|
|
|
1434
|
|
|
$nameLoop = ($name = $val->name); |
1435
|
|
|
}elseif( !$isCall && $this->MatchReg('/\\G\.{3}/') ){ |
1436
|
|
|
$returner['variadic'] = true; |
1437
|
|
|
if( $this->MatchChar(";") && !$isSemiColonSeperated ){ |
|
|
|
|
1438
|
|
|
$isSemiColonSeperated = true; |
1439
|
|
|
} |
1440
|
|
|
if( $isSemiColonSeperated ){ |
1441
|
|
|
$argsSemiColon[] = array('name'=> $arg->name, 'variadic' => true); |
1442
|
|
|
}else{ |
1443
|
|
|
$argsComma[] = array('name'=> $arg->name, 'variadic' => true); |
1444
|
|
|
} |
1445
|
|
|
break; |
1446
|
|
|
}elseif( !$isCall ){ |
1447
|
|
|
$name = $nameLoop = $val->name; |
1448
|
|
|
$value = null; |
1449
|
|
|
} |
1450
|
|
|
} |
1451
|
|
|
|
1452
|
|
|
if( $value ){ |
1453
|
|
|
$expressions[] = $value; |
1454
|
|
|
} |
1455
|
|
|
|
1456
|
|
|
$argsComma[] = array('name'=>$nameLoop, 'value'=>$value ); |
1457
|
|
|
|
1458
|
|
|
if( $this->MatchChar(',') ){ |
1459
|
|
|
continue; |
1460
|
|
|
} |
1461
|
|
|
|
1462
|
|
|
if( $this->MatchChar(';') || $isSemiColonSeperated ){ |
1463
|
|
|
|
1464
|
|
|
if( $expressionContainsNamed ){ |
1465
|
|
|
$this->Error('Cannot mix ; and , as delimiter types'); |
1466
|
|
|
} |
1467
|
|
|
|
1468
|
|
|
$isSemiColonSeperated = true; |
1469
|
|
|
|
1470
|
|
|
if( count($expressions) > 1 ){ |
1471
|
|
|
$value = $this->NewObj1('Less_Tree_Value', $expressions); |
1472
|
|
|
} |
1473
|
|
|
$argsSemiColon[] = array('name'=>$name, 'value'=>$value ); |
1474
|
|
|
|
1475
|
|
|
$name = null; |
1476
|
|
|
$expressions = array(); |
1477
|
|
|
$expressionContainsNamed = false; |
1478
|
|
|
} |
1479
|
|
|
} |
1480
|
|
|
|
1481
|
|
|
$this->forget(); |
1482
|
|
|
$returner['args'] = ($isSemiColonSeperated ? $argsSemiColon : $argsComma); |
1483
|
|
|
return $returner; |
1484
|
|
|
} |
1485
|
|
|
|
1486
|
|
|
|
1487
|
|
|
|
1488
|
|
|
// |
1489
|
|
|
// A Mixin definition, with a list of parameters |
1490
|
|
|
// |
1491
|
|
|
// .rounded (@radius: 2px, @color) { |
1492
|
|
|
// ... |
1493
|
|
|
// } |
1494
|
|
|
// |
1495
|
|
|
// Until we have a finer grained state-machine, we have to |
1496
|
|
|
// do a look-ahead, to make sure we don't have a mixin call. |
1497
|
|
|
// See the `rule` function for more information. |
1498
|
|
|
// |
1499
|
|
|
// We start by matching `.rounded (`, and then proceed on to |
1500
|
|
|
// the argument list, which has optional default values. |
1501
|
|
|
// We store the parameters in `params`, with a `value` key, |
1502
|
|
|
// if there is a value, such as in the case of `@radius`. |
1503
|
|
|
// |
1504
|
|
|
// Once we've got our params list, and a closing `)`, we parse |
1505
|
|
|
// the `{...}` block. |
1506
|
|
|
// |
1507
|
|
|
private function parseMixinDefinition(){ |
1508
|
|
|
$cond = null; |
1509
|
|
|
|
1510
|
|
|
$char = $this->input[$this->pos]; |
1511
|
|
|
if( ($char !== '.' && $char !== '#') || ($char === '{' && $this->PeekReg('/\\G[^{]*\}/')) ){ |
1512
|
|
|
return; |
1513
|
|
|
} |
1514
|
|
|
|
1515
|
|
|
$this->save(); |
1516
|
|
|
|
1517
|
|
|
$match = $this->MatchReg('/\\G([#.](?:[\w-]|\\\(?:[A-Fa-f0-9]{1,6} ?|[^A-Fa-f0-9]))+)\s*\(/'); |
1518
|
|
|
if( $match ){ |
1519
|
|
|
$name = $match[1]; |
1520
|
|
|
|
1521
|
|
|
$argInfo = $this->parseMixinArgs( false ); |
1522
|
|
|
$params = $argInfo['args']; |
1523
|
|
|
$variadic = $argInfo['variadic']; |
1524
|
|
|
|
1525
|
|
|
|
1526
|
|
|
// .mixincall("@{a}"); |
1527
|
|
|
// looks a bit like a mixin definition.. |
1528
|
|
|
// also |
1529
|
|
|
// .mixincall(@a: {rule: set;}); |
1530
|
|
|
// so we have to be nice and restore |
1531
|
|
|
if( !$this->MatchChar(')') ){ |
1532
|
|
|
$this->furthest = $this->pos; |
1533
|
|
|
$this->restore(); |
1534
|
|
|
return; |
1535
|
|
|
} |
1536
|
|
|
|
1537
|
|
|
|
1538
|
|
|
$this->parseComments(); |
1539
|
|
|
|
1540
|
|
|
if ($this->MatchReg('/\\Gwhen/')) { // Guard |
1541
|
|
|
$cond = $this->expect('parseConditions', 'Expected conditions'); |
1542
|
|
|
} |
1543
|
|
|
|
1544
|
|
|
$ruleset = $this->parseBlock(); |
1545
|
|
|
|
1546
|
|
|
if( is_array($ruleset) ){ |
1547
|
|
|
$this->forget(); |
1548
|
|
|
return $this->NewObj5('Less_Tree_Mixin_Definition', array( $name, $params, $ruleset, $cond, $variadic)); |
1549
|
|
|
} |
1550
|
|
|
|
1551
|
|
|
$this->restore(); |
1552
|
|
|
}else{ |
1553
|
|
|
$this->forget(); |
1554
|
|
|
} |
1555
|
|
|
} |
1556
|
|
|
|
1557
|
|
|
// |
1558
|
|
|
// Entities are the smallest recognized token, |
1559
|
|
|
// and can be found inside a rule's value. |
1560
|
|
|
// |
1561
|
|
|
private function parseEntity(){ |
1562
|
|
|
|
1563
|
|
|
return $this->MatchFuncs( array('parseEntitiesLiteral','parseEntitiesVariable','parseEntitiesUrl','parseEntitiesCall','parseEntitiesKeyword','parseEntitiesJavascript','parseComment') ); |
1564
|
|
|
} |
1565
|
|
|
|
1566
|
|
|
// |
1567
|
|
|
// A Rule terminator. Note that we use `peek()` to check for '}', |
1568
|
|
|
// because the `block` rule will be expecting it, but we still need to make sure |
1569
|
|
|
// it's there, if ';' was omitted. |
1570
|
|
|
// |
1571
|
|
|
private function parseEnd(){ |
1572
|
|
|
return $this->MatchChar(';') || $this->PeekChar('}'); |
1573
|
|
|
} |
1574
|
|
|
|
1575
|
|
|
// |
1576
|
|
|
// IE's alpha function |
1577
|
|
|
// |
1578
|
|
|
// alpha(opacity=88) |
1579
|
|
|
// |
1580
|
|
|
private function parseAlpha(){ |
1581
|
|
|
|
1582
|
|
|
if ( ! $this->MatchReg('/\\G\(opacity=/i')) { |
1583
|
|
|
return; |
1584
|
|
|
} |
1585
|
|
|
|
1586
|
|
|
$value = $this->MatchReg('/\\G[0-9]+/'); |
1587
|
|
|
if( $value ){ |
|
|
|
|
1588
|
|
|
$value = $value[0]; |
1589
|
|
|
}else{ |
1590
|
|
|
$value = $this->parseEntitiesVariable(); |
1591
|
|
|
if( !$value ){ |
1592
|
|
|
return; |
1593
|
|
|
} |
1594
|
|
|
} |
1595
|
|
|
|
1596
|
|
|
$this->expectChar(')'); |
1597
|
|
|
return $this->NewObj1('Less_Tree_Alpha',$value); |
1598
|
|
|
} |
1599
|
|
|
|
1600
|
|
|
|
1601
|
|
|
// |
1602
|
|
|
// A Selector Element |
1603
|
|
|
// |
1604
|
|
|
// div |
1605
|
|
|
// + h1 |
1606
|
|
|
// #socks |
1607
|
|
|
// input[type="text"] |
1608
|
|
|
// |
1609
|
|
|
// Elements are the building blocks for Selectors, |
1610
|
|
|
// they are made out of a `Combinator` (see combinator rule), |
1611
|
|
|
// and an element name, such as a tag a class, or `*`. |
1612
|
|
|
// |
1613
|
|
|
private function parseElement(){ |
1614
|
|
|
$c = $this->parseCombinator(); |
1615
|
|
|
$index = $this->pos; |
1616
|
|
|
|
1617
|
|
|
$e = $this->match( array('/\\G(?:\d+\.\d+|\d+)%/', '/\\G(?:[.#]?|:*)(?:[\w-]|[^\x00-\x9f]|\\\\(?:[A-Fa-f0-9]{1,6} ?|[^A-Fa-f0-9]))+/', |
1618
|
|
|
'#*', '#&', 'parseAttribute', '/\\G\([^()@]+\)/', '/\\G[\.#](?=@)/', 'parseEntitiesVariableCurly') ); |
1619
|
|
|
|
1620
|
|
|
if( is_null($e) ){ |
1621
|
|
|
$this->save(); |
1622
|
|
|
if( $this->MatchChar('(') ){ |
1623
|
|
|
if( ($v = $this->parseSelector()) && $this->MatchChar(')') ){ |
1624
|
|
|
$e = $this->NewObj1('Less_Tree_Paren',$v); |
1625
|
|
|
$this->forget(); |
1626
|
|
|
}else{ |
1627
|
|
|
$this->restore(); |
1628
|
|
|
} |
1629
|
|
|
}else{ |
1630
|
|
|
$this->forget(); |
1631
|
|
|
} |
1632
|
|
|
} |
1633
|
|
|
|
1634
|
|
|
if( !is_null($e) ){ |
1635
|
|
|
return $this->NewObj4('Less_Tree_Element',array( $c, $e, $index, $this->env->currentFileInfo)); |
1636
|
|
|
} |
1637
|
|
|
} |
1638
|
|
|
|
1639
|
|
|
// |
1640
|
|
|
// Combinators combine elements together, in a Selector. |
1641
|
|
|
// |
1642
|
|
|
// Because our parser isn't white-space sensitive, special care |
1643
|
|
|
// has to be taken, when parsing the descendant combinator, ` `, |
1644
|
|
|
// as it's an empty space. We have to check the previous character |
1645
|
|
|
// in the input, to see if it's a ` ` character. |
1646
|
|
|
// |
1647
|
|
|
private function parseCombinator(){ |
1648
|
|
|
if( $this->pos < $this->input_len ){ |
1649
|
|
|
$c = $this->input[$this->pos]; |
1650
|
|
|
if ($c === '>' || $c === '+' || $c === '~' || $c === '|' || $c === '^' ){ |
1651
|
|
|
|
1652
|
|
|
$this->pos++; |
1653
|
|
|
if( $this->input[$this->pos] === '^' ){ |
1654
|
|
|
$c = '^^'; |
1655
|
|
|
$this->pos++; |
1656
|
|
|
} |
1657
|
|
|
|
1658
|
|
|
$this->skipWhitespace(0); |
1659
|
|
|
|
1660
|
|
|
return $c; |
1661
|
|
|
} |
1662
|
|
|
|
1663
|
|
|
if( $this->pos > 0 && $this->isWhitespace(-1) ){ |
1664
|
|
|
return ' '; |
1665
|
|
|
} |
1666
|
|
|
} |
1667
|
|
|
} |
1668
|
|
|
|
1669
|
|
|
// |
1670
|
|
|
// A CSS selector (see selector below) |
1671
|
|
|
// with less extensions e.g. the ability to extend and guard |
1672
|
|
|
// |
1673
|
|
|
private function parseLessSelector(){ |
1674
|
|
|
return $this->parseSelector(true); |
1675
|
|
|
} |
1676
|
|
|
|
1677
|
|
|
// |
1678
|
|
|
// A CSS Selector |
1679
|
|
|
// |
1680
|
|
|
// .class > div + h1 |
1681
|
|
|
// li a:hover |
1682
|
|
|
// |
1683
|
|
|
// Selectors are made out of one or more Elements, see above. |
1684
|
|
|
// |
1685
|
|
|
private function parseSelector( $isLess = false ){ |
1686
|
|
|
$elements = array(); |
1687
|
|
|
$extendList = array(); |
1688
|
|
|
$condition = null; |
1689
|
|
|
$when = false; |
1690
|
|
|
$extend = false; |
1691
|
|
|
$e = null; |
1692
|
|
|
$c = null; |
1693
|
|
|
$index = $this->pos; |
1694
|
|
|
|
1695
|
|
|
while( ($isLess && ($extend = $this->parseExtend())) || ($isLess && ($when = $this->MatchReg('/\\Gwhen/') )) || ($e = $this->parseElement()) ){ |
1696
|
|
|
if( $when ){ |
1697
|
|
|
$condition = $this->expect('parseConditions', 'expected condition'); |
1698
|
|
|
}elseif( $condition ){ |
1699
|
|
|
//error("CSS guard can only be used at the end of selector"); |
1700
|
|
|
}elseif( $extend ){ |
1701
|
|
|
$extendList = array_merge($extendList,$extend); |
1702
|
|
|
}else{ |
1703
|
|
|
//if( count($extendList) ){ |
1704
|
|
|
//error("Extend can only be used at the end of selector"); |
1705
|
|
|
//} |
1706
|
|
|
if( $this->pos < $this->input_len ){ |
1707
|
|
|
$c = $this->input[ $this->pos ]; |
1708
|
|
|
} |
1709
|
|
|
$elements[] = $e; |
1710
|
|
|
$e = null; |
1711
|
|
|
} |
1712
|
|
|
|
1713
|
|
|
if( $c === '{' || $c === '}' || $c === ';' || $c === ',' || $c === ')') { break; } |
1714
|
|
|
} |
1715
|
|
|
|
1716
|
|
|
if( $elements ){ |
|
|
|
|
1717
|
|
|
return $this->NewObj5('Less_Tree_Selector',array($elements, $extendList, $condition, $index, $this->env->currentFileInfo)); |
1718
|
|
|
} |
1719
|
|
|
if( $extendList ) { |
|
|
|
|
1720
|
|
|
$this->Error('Extend must be used to extend a selector, it cannot be used on its own'); |
1721
|
|
|
} |
1722
|
|
|
} |
1723
|
|
|
|
1724
|
|
|
private function parseTag(){ |
1725
|
|
|
return ( $tag = $this->MatchReg('/\\G[A-Za-z][A-Za-z-]*[0-9]?/') ) ? $tag : $this->MatchChar('*'); |
1726
|
|
|
} |
1727
|
|
|
|
1728
|
|
|
private function parseAttribute(){ |
1729
|
|
|
|
1730
|
|
|
$val = null; |
1731
|
|
|
|
1732
|
|
|
if( !$this->MatchChar('[') ){ |
1733
|
|
|
return; |
1734
|
|
|
} |
1735
|
|
|
|
1736
|
|
|
$key = $this->parseEntitiesVariableCurly(); |
1737
|
|
|
if( !$key ){ |
1738
|
|
|
$key = $this->expect('/\\G(?:[_A-Za-z0-9-\*]*\|)?(?:[_A-Za-z0-9-]|\\\\.)+/'); |
1739
|
|
|
} |
1740
|
|
|
|
1741
|
|
|
$op = $this->MatchReg('/\\G[|~*$^]?=/'); |
1742
|
|
|
if( $op ){ |
1743
|
|
|
$val = $this->match( array('parseEntitiesQuoted','/\\G[0-9]+%/','/\\G[\w-]+/','parseEntitiesVariableCurly') ); |
1744
|
|
|
} |
1745
|
|
|
|
1746
|
|
|
$this->expectChar(']'); |
1747
|
|
|
|
1748
|
|
|
return $this->NewObj3('Less_Tree_Attribute',array( $key, $op[0], $val)); |
1749
|
|
|
} |
1750
|
|
|
|
1751
|
|
|
// |
1752
|
|
|
// The `block` rule is used by `ruleset` and `mixin.definition`. |
1753
|
|
|
// It's a wrapper around the `primary` rule, with added `{}`. |
1754
|
|
|
// |
1755
|
|
|
private function parseBlock(){ |
1756
|
|
|
if( $this->MatchChar('{') ){ |
1757
|
|
|
$content = $this->parsePrimary(); |
1758
|
|
|
if( $this->MatchChar('}') ){ |
1759
|
|
|
return $content; |
1760
|
|
|
} |
1761
|
|
|
} |
1762
|
|
|
} |
1763
|
|
|
|
1764
|
|
|
private function parseBlockRuleset(){ |
1765
|
|
|
$block = $this->parseBlock(); |
1766
|
|
|
|
1767
|
|
|
if( $block ){ |
1768
|
|
|
$block = $this->NewObj2('Less_Tree_Ruleset',array( null, $block)); |
1769
|
|
|
} |
1770
|
|
|
|
1771
|
|
|
return $block; |
1772
|
|
|
} |
1773
|
|
|
|
1774
|
|
|
private function parseDetachedRuleset(){ |
1775
|
|
|
$blockRuleset = $this->parseBlockRuleset(); |
1776
|
|
|
if( $blockRuleset ){ |
1777
|
|
|
return $this->NewObj1('Less_Tree_DetachedRuleset',$blockRuleset); |
1778
|
|
|
} |
1779
|
|
|
} |
1780
|
|
|
|
1781
|
|
|
// |
1782
|
|
|
// div, .class, body > p {...} |
1783
|
|
|
// |
1784
|
|
|
private function parseRuleset(){ |
1785
|
|
|
$selectors = array(); |
1786
|
|
|
|
1787
|
|
|
$this->save(); |
1788
|
|
|
|
1789
|
|
|
while( true ){ |
1790
|
|
|
$s = $this->parseLessSelector(); |
1791
|
|
|
if( !$s ){ |
1792
|
|
|
break; |
1793
|
|
|
} |
1794
|
|
|
$selectors[] = $s; |
1795
|
|
|
$this->parseComments(); |
1796
|
|
|
|
1797
|
|
|
if( $s->condition && count($selectors) > 1 ){ |
1798
|
|
|
$this->Error('Guards are only currently allowed on a single selector.'); |
1799
|
|
|
} |
1800
|
|
|
|
1801
|
|
|
if( !$this->MatchChar(',') ){ |
1802
|
|
|
break; |
1803
|
|
|
} |
1804
|
|
|
if( $s->condition ){ |
1805
|
|
|
$this->Error('Guards are only currently allowed on a single selector.'); |
1806
|
|
|
} |
1807
|
|
|
$this->parseComments(); |
1808
|
|
|
} |
1809
|
|
|
|
1810
|
|
|
|
1811
|
|
|
if( $selectors ){ |
|
|
|
|
1812
|
|
|
$rules = $this->parseBlock(); |
1813
|
|
|
if( is_array($rules) ){ |
1814
|
|
|
$this->forget(); |
1815
|
|
|
return $this->NewObj2('Less_Tree_Ruleset',array( $selectors, $rules)); //Less_Environment::$strictImports |
1816
|
|
|
} |
1817
|
|
|
} |
1818
|
|
|
|
1819
|
|
|
// Backtrack |
1820
|
|
|
$this->furthest = $this->pos; |
1821
|
|
|
$this->restore(); |
1822
|
|
|
} |
1823
|
|
|
|
1824
|
|
|
/** |
1825
|
|
|
* Custom less.php parse function for finding simple name-value css pairs |
1826
|
|
|
* ex: width:100px; |
1827
|
|
|
* |
1828
|
|
|
*/ |
1829
|
|
|
private function parseNameValue(){ |
1830
|
|
|
|
1831
|
|
|
$index = $this->pos; |
1832
|
|
|
$this->save(); |
1833
|
|
|
|
1834
|
|
|
|
1835
|
|
|
//$match = $this->MatchReg('/\\G([a-zA-Z\-]+)\s*:\s*((?:\'")?[a-zA-Z0-9\-% \.,!]+?(?:\'")?)\s*([;}])/'); |
1836
|
|
|
$match = $this->MatchReg('/\\G([a-zA-Z\-]+)\s*:\s*([\'"]?[#a-zA-Z0-9\-%\.,]+?[\'"]?) *(! *important)?\s*([;}])/'); |
1837
|
|
|
if( $match ){ |
1838
|
|
|
|
1839
|
|
|
if( $match[4] == '}' ){ |
1840
|
|
|
$this->pos = $index + strlen($match[0])-1; |
1841
|
|
|
} |
1842
|
|
|
|
1843
|
|
|
if( $match[3] ){ |
1844
|
|
|
$match[2] .= ' !important'; |
1845
|
|
|
} |
1846
|
|
|
|
1847
|
|
|
return $this->NewObj4('Less_Tree_NameValue',array( $match[1], $match[2], $index, $this->env->currentFileInfo)); |
1848
|
|
|
} |
1849
|
|
|
|
1850
|
|
|
$this->restore(); |
1851
|
|
|
} |
1852
|
|
|
|
1853
|
|
|
|
1854
|
|
|
private function parseRule( $tryAnonymous = null ){ |
1855
|
|
|
|
1856
|
|
|
$merge = false; |
1857
|
|
|
$startOfRule = $this->pos; |
1858
|
|
|
|
1859
|
|
|
$c = $this->input[$this->pos]; |
1860
|
|
|
if( $c === '.' || $c === '#' || $c === '&' ){ |
1861
|
|
|
return; |
1862
|
|
|
} |
1863
|
|
|
|
1864
|
|
|
$this->save(); |
1865
|
|
|
$name = $this->MatchFuncs( array('parseVariable','parseRuleProperty')); |
1866
|
|
|
|
1867
|
|
|
if( $name ){ |
1868
|
|
|
|
1869
|
|
|
$isVariable = is_string($name); |
1870
|
|
|
|
1871
|
|
|
$value = null; |
1872
|
|
|
if( $isVariable ){ |
1873
|
|
|
$value = $this->parseDetachedRuleset(); |
1874
|
|
|
} |
1875
|
|
|
|
1876
|
|
|
$important = null; |
1877
|
|
|
if( !$value ){ |
1878
|
|
|
|
1879
|
|
|
// prefer to try to parse first if its a variable or we are compressing |
1880
|
|
|
// but always fallback on the other one |
1881
|
|
|
//if( !$tryAnonymous && is_string($name) && $name[0] === '@' ){ |
1882
|
|
|
if( !$tryAnonymous && (Less_Parser::$options['compress'] || $isVariable) ){ |
1883
|
|
|
$value = $this->MatchFuncs( array('parseValue','parseAnonymousValue')); |
1884
|
|
|
}else{ |
1885
|
|
|
$value = $this->MatchFuncs( array('parseAnonymousValue','parseValue')); |
1886
|
|
|
} |
1887
|
|
|
|
1888
|
|
|
$important = $this->parseImportant(); |
1889
|
|
|
|
1890
|
|
|
// a name returned by this.ruleProperty() is always an array of the form: |
1891
|
|
|
// [string-1, ..., string-n, ""] or [string-1, ..., string-n, "+"] |
1892
|
|
|
// where each item is a tree.Keyword or tree.Variable |
1893
|
|
|
if( !$isVariable && is_array($name) ){ |
1894
|
|
|
$nm = array_pop($name); |
1895
|
|
|
if( $nm->value ){ |
1896
|
|
|
$merge = $nm->value; |
1897
|
|
|
} |
1898
|
|
|
} |
1899
|
|
|
} |
1900
|
|
|
|
1901
|
|
|
|
1902
|
|
|
if( $value && $this->parseEnd() ){ |
1903
|
|
|
$this->forget(); |
1904
|
|
|
return $this->NewObj6('Less_Tree_Rule',array( $name, $value, $important, $merge, $startOfRule, $this->env->currentFileInfo)); |
1905
|
|
|
}else{ |
1906
|
|
|
$this->furthest = $this->pos; |
1907
|
|
|
$this->restore(); |
1908
|
|
|
if( $value && !$tryAnonymous ){ |
1909
|
|
|
return $this->parseRule(true); |
1910
|
|
|
} |
1911
|
|
|
} |
1912
|
|
|
}else{ |
1913
|
|
|
$this->forget(); |
1914
|
|
|
} |
1915
|
|
|
} |
1916
|
|
|
|
1917
|
|
|
function parseAnonymousValue(){ |
|
|
|
|
1918
|
|
|
|
1919
|
|
|
if( preg_match('/\\G([^@+\/\'"*`(;{}-]*);/',$this->input, $match, 0, $this->pos) ){ |
1920
|
|
|
$this->pos += strlen($match[1]); |
1921
|
|
|
return $this->NewObj1('Less_Tree_Anonymous',$match[1]); |
1922
|
|
|
} |
1923
|
|
|
} |
1924
|
|
|
|
1925
|
|
|
// |
1926
|
|
|
// An @import directive |
1927
|
|
|
// |
1928
|
|
|
// @import "lib"; |
1929
|
|
|
// |
1930
|
|
|
// Depending on our environment, importing is done differently: |
1931
|
|
|
// In the browser, it's an XHR request, in Node, it would be a |
1932
|
|
|
// file-system operation. The function used for importing is |
1933
|
|
|
// stored in `import`, which we pass to the Import constructor. |
1934
|
|
|
// |
1935
|
|
|
private function parseImport(){ |
1936
|
|
|
|
1937
|
|
|
$this->save(); |
1938
|
|
|
|
1939
|
|
|
$dir = $this->MatchReg('/\\G@import?\s+/'); |
1940
|
|
|
|
1941
|
|
|
if( $dir ){ |
1942
|
|
|
$options = $this->parseImportOptions(); |
1943
|
|
|
$path = $this->MatchFuncs( array('parseEntitiesQuoted','parseEntitiesUrl')); |
1944
|
|
|
|
1945
|
|
|
if( $path ){ |
1946
|
|
|
$features = $this->parseMediaFeatures(); |
1947
|
|
|
if( $this->MatchChar(';') ){ |
1948
|
|
|
if( $features ){ |
1949
|
|
|
$features = $this->NewObj1('Less_Tree_Value',$features); |
1950
|
|
|
} |
1951
|
|
|
|
1952
|
|
|
$this->forget(); |
1953
|
|
|
return $this->NewObj5('Less_Tree_Import',array( $path, $features, $options, $this->pos, $this->env->currentFileInfo)); |
1954
|
|
|
} |
1955
|
|
|
} |
1956
|
|
|
} |
1957
|
|
|
|
1958
|
|
|
$this->restore(); |
1959
|
|
|
} |
1960
|
|
|
|
1961
|
|
|
private function parseImportOptions(){ |
1962
|
|
|
|
1963
|
|
|
$options = array(); |
1964
|
|
|
|
1965
|
|
|
// list of options, surrounded by parens |
1966
|
|
|
if( !$this->MatchChar('(') ){ |
1967
|
|
|
return $options; |
1968
|
|
|
} |
1969
|
|
|
do{ |
1970
|
|
|
$optionName = $this->parseImportOption(); |
1971
|
|
|
if( $optionName ){ |
1972
|
|
|
$value = true; |
1973
|
|
|
switch( $optionName ){ |
1974
|
|
|
case "css": |
1975
|
|
|
$optionName = "less"; |
1976
|
|
|
$value = false; |
1977
|
|
|
break; |
1978
|
|
|
case "once": |
1979
|
|
|
$optionName = "multiple"; |
1980
|
|
|
$value = false; |
1981
|
|
|
break; |
1982
|
|
|
} |
1983
|
|
|
$options[$optionName] = $value; |
1984
|
|
|
if( !$this->MatchChar(',') ){ break; } |
1985
|
|
|
} |
1986
|
|
|
}while( $optionName ); |
1987
|
|
|
$this->expectChar(')'); |
1988
|
|
|
return $options; |
1989
|
|
|
} |
1990
|
|
|
|
1991
|
|
|
private function parseImportOption(){ |
1992
|
|
|
$opt = $this->MatchReg('/\\G(less|css|multiple|once|inline|reference)/'); |
1993
|
|
|
if( $opt ){ |
1994
|
|
|
return $opt[1]; |
1995
|
|
|
} |
1996
|
|
|
} |
1997
|
|
|
|
1998
|
|
|
private function parseMediaFeature() { |
1999
|
|
|
$nodes = array(); |
2000
|
|
|
|
2001
|
|
|
do{ |
2002
|
|
|
$e = $this->MatchFuncs(array('parseEntitiesKeyword','parseEntitiesVariable')); |
2003
|
|
|
if( $e ){ |
2004
|
|
|
$nodes[] = $e; |
2005
|
|
|
} elseif ($this->MatchChar('(')) { |
2006
|
|
|
$p = $this->parseProperty(); |
2007
|
|
|
$e = $this->parseValue(); |
2008
|
|
|
if ($this->MatchChar(')')) { |
2009
|
|
|
if ($p && $e) { |
2010
|
|
|
$r = $this->NewObj7('Less_Tree_Rule', array( $p, $e, null, null, $this->pos, $this->env->currentFileInfo, true)); |
2011
|
|
|
$nodes[] = $this->NewObj1('Less_Tree_Paren',$r); |
2012
|
|
|
} elseif ($e) { |
2013
|
|
|
$nodes[] = $this->NewObj1('Less_Tree_Paren',$e); |
2014
|
|
|
} else { |
2015
|
|
|
return null; |
2016
|
|
|
} |
2017
|
|
|
} else |
2018
|
|
|
return null; |
2019
|
|
|
} |
2020
|
|
|
} while ($e); |
2021
|
|
|
|
2022
|
|
|
if ($nodes) { |
|
|
|
|
2023
|
|
|
return $this->NewObj1('Less_Tree_Expression',$nodes); |
2024
|
|
|
} |
2025
|
|
|
} |
2026
|
|
|
|
2027
|
|
|
private function parseMediaFeatures() { |
2028
|
|
|
$features = array(); |
2029
|
|
|
|
2030
|
|
|
do{ |
2031
|
|
|
$e = $this->parseMediaFeature(); |
2032
|
|
|
if( $e ){ |
2033
|
|
|
$features[] = $e; |
2034
|
|
|
if (!$this->MatchChar(',')) break; |
2035
|
|
|
}else{ |
2036
|
|
|
$e = $this->parseEntitiesVariable(); |
2037
|
|
|
if( $e ){ |
2038
|
|
|
$features[] = $e; |
2039
|
|
|
if (!$this->MatchChar(',')) break; |
2040
|
|
|
} |
2041
|
|
|
} |
2042
|
|
|
} while ($e); |
2043
|
|
|
|
2044
|
|
|
return $features ? $features : null; |
2045
|
|
|
} |
2046
|
|
|
|
2047
|
|
|
private function parseMedia() { |
2048
|
|
|
if( $this->MatchReg('/\\G@media/') ){ |
2049
|
|
|
$features = $this->parseMediaFeatures(); |
2050
|
|
|
$rules = $this->parseBlock(); |
2051
|
|
|
|
2052
|
|
|
if( is_array($rules) ){ |
2053
|
|
|
return $this->NewObj4('Less_Tree_Media',array( $rules, $features, $this->pos, $this->env->currentFileInfo)); |
2054
|
|
|
} |
2055
|
|
|
} |
2056
|
|
|
} |
2057
|
|
|
|
2058
|
|
|
|
2059
|
|
|
// |
2060
|
|
|
// A CSS Directive |
2061
|
|
|
// |
2062
|
|
|
// @charset "utf-8"; |
2063
|
|
|
// |
2064
|
|
|
private function parseDirective(){ |
2065
|
|
|
|
2066
|
|
|
if( !$this->PeekChar('@') ){ |
2067
|
|
|
return; |
2068
|
|
|
} |
2069
|
|
|
|
2070
|
|
|
$rules = null; |
2071
|
|
|
$index = $this->pos; |
2072
|
|
|
$hasBlock = true; |
2073
|
|
|
$hasIdentifier = false; |
2074
|
|
|
$hasExpression = false; |
2075
|
|
|
$hasUnknown = false; |
2076
|
|
|
|
2077
|
|
|
|
2078
|
|
|
$value = $this->MatchFuncs(array('parseImport','parseMedia')); |
2079
|
|
|
if( $value ){ |
2080
|
|
|
return $value; |
2081
|
|
|
} |
2082
|
|
|
|
2083
|
|
|
$this->save(); |
2084
|
|
|
|
2085
|
|
|
$name = $this->MatchReg('/\\G@[a-z-]+/'); |
2086
|
|
|
|
2087
|
|
|
if( !$name ) return; |
2088
|
|
|
$name = $name[0]; |
2089
|
|
|
|
2090
|
|
|
|
2091
|
|
|
$nonVendorSpecificName = $name; |
2092
|
|
|
$pos = strpos($name,'-', 2); |
2093
|
|
|
if( $name[1] == '-' && $pos > 0 ){ |
2094
|
|
|
$nonVendorSpecificName = "@" . substr($name, $pos + 1); |
2095
|
|
|
} |
2096
|
|
|
|
2097
|
|
|
|
2098
|
|
|
switch( $nonVendorSpecificName ){ |
2099
|
|
|
/* |
2100
|
|
|
case "@font-face": |
2101
|
|
|
case "@viewport": |
2102
|
|
|
case "@top-left": |
2103
|
|
|
case "@top-left-corner": |
2104
|
|
|
case "@top-center": |
2105
|
|
|
case "@top-right": |
2106
|
|
|
case "@top-right-corner": |
2107
|
|
|
case "@bottom-left": |
2108
|
|
|
case "@bottom-left-corner": |
2109
|
|
|
case "@bottom-center": |
2110
|
|
|
case "@bottom-right": |
2111
|
|
|
case "@bottom-right-corner": |
2112
|
|
|
case "@left-top": |
2113
|
|
|
case "@left-middle": |
2114
|
|
|
case "@left-bottom": |
2115
|
|
|
case "@right-top": |
2116
|
|
|
case "@right-middle": |
2117
|
|
|
case "@right-bottom": |
2118
|
|
|
hasBlock = true; |
2119
|
|
|
break; |
2120
|
|
|
*/ |
2121
|
|
|
case "@charset": |
2122
|
|
|
$hasIdentifier = true; |
2123
|
|
|
$hasBlock = false; |
2124
|
|
|
break; |
2125
|
|
|
case "@namespace": |
2126
|
|
|
$hasExpression = true; |
2127
|
|
|
$hasBlock = false; |
2128
|
|
|
break; |
2129
|
|
|
case "@keyframes": |
2130
|
|
|
$hasIdentifier = true; |
2131
|
|
|
break; |
2132
|
|
|
case "@host": |
2133
|
|
|
case "@page": |
2134
|
|
|
case "@document": |
2135
|
|
|
case "@supports": |
2136
|
|
|
$hasUnknown = true; |
2137
|
|
|
break; |
2138
|
|
|
} |
2139
|
|
|
|
2140
|
|
|
if( $hasIdentifier ){ |
2141
|
|
|
$value = $this->parseEntity(); |
2142
|
|
|
if( !$value ){ |
2143
|
|
|
$this->error("expected " . $name . " identifier"); |
2144
|
|
|
} |
2145
|
|
|
} else if( $hasExpression ){ |
2146
|
|
|
$value = $this->parseExpression(); |
2147
|
|
|
if( !$value ){ |
2148
|
|
|
$this->error("expected " . $name. " expression"); |
2149
|
|
|
} |
2150
|
|
|
} else if ($hasUnknown) { |
2151
|
|
|
|
2152
|
|
|
$value = $this->MatchReg('/\\G[^{;]+/'); |
2153
|
|
|
if( $value ){ |
|
|
|
|
2154
|
|
|
$value = $this->NewObj1('Less_Tree_Anonymous',trim($value[0])); |
2155
|
|
|
} |
2156
|
|
|
} |
2157
|
|
|
|
2158
|
|
|
if( $hasBlock ){ |
2159
|
|
|
$rules = $this->parseBlockRuleset(); |
2160
|
|
|
} |
2161
|
|
|
|
2162
|
|
|
if( $rules || (!$hasBlock && $value && $this->MatchChar(';'))) { |
2163
|
|
|
$this->forget(); |
2164
|
|
|
return $this->NewObj5('Less_Tree_Directive',array($name, $value, $rules, $index, $this->env->currentFileInfo)); |
2165
|
|
|
} |
2166
|
|
|
|
2167
|
|
|
$this->restore(); |
2168
|
|
|
} |
2169
|
|
|
|
2170
|
|
|
|
2171
|
|
|
// |
2172
|
|
|
// A Value is a comma-delimited list of Expressions |
2173
|
|
|
// |
2174
|
|
|
// font-family: Baskerville, Georgia, serif; |
2175
|
|
|
// |
2176
|
|
|
// In a Rule, a Value represents everything after the `:`, |
2177
|
|
|
// and before the `;`. |
2178
|
|
|
// |
2179
|
|
|
private function parseValue(){ |
2180
|
|
|
$expressions = array(); |
2181
|
|
|
|
2182
|
|
|
do{ |
2183
|
|
|
$e = $this->parseExpression(); |
2184
|
|
|
if( $e ){ |
2185
|
|
|
$expressions[] = $e; |
2186
|
|
|
if (! $this->MatchChar(',')) { |
2187
|
|
|
break; |
2188
|
|
|
} |
2189
|
|
|
} |
2190
|
|
|
}while($e); |
2191
|
|
|
|
2192
|
|
|
if( $expressions ){ |
|
|
|
|
2193
|
|
|
return $this->NewObj1('Less_Tree_Value',$expressions); |
2194
|
|
|
} |
2195
|
|
|
} |
2196
|
|
|
|
2197
|
|
|
private function parseImportant (){ |
2198
|
|
|
if( $this->PeekChar('!') && $this->MatchReg('/\\G! *important/') ){ |
2199
|
|
|
return ' !important'; |
2200
|
|
|
} |
2201
|
|
|
} |
2202
|
|
|
|
2203
|
|
|
private function parseSub (){ |
2204
|
|
|
|
2205
|
|
|
if( $this->MatchChar('(') ){ |
2206
|
|
|
$a = $this->parseAddition(); |
2207
|
|
|
if( $a ){ |
2208
|
|
|
$this->expectChar(')'); |
2209
|
|
|
return $this->NewObj2('Less_Tree_Expression',array( array($a), true) ); //instead of $e->parens = true so the value is cached |
2210
|
|
|
} |
2211
|
|
|
} |
2212
|
|
|
} |
2213
|
|
|
|
2214
|
|
|
|
2215
|
|
|
/** |
2216
|
|
|
* Parses multiplication operation |
2217
|
|
|
* |
2218
|
|
|
* @return Less_Tree_Operation|null |
2219
|
|
|
*/ |
2220
|
|
|
function parseMultiplication(){ |
|
|
|
|
2221
|
|
|
|
2222
|
|
|
$return = $m = $this->parseOperand(); |
2223
|
|
|
if( $return ){ |
2224
|
|
|
while( true ){ |
2225
|
|
|
|
2226
|
|
|
$isSpaced = $this->isWhitespace( -1 ); |
2227
|
|
|
|
2228
|
|
|
if( $this->PeekReg('/\\G\/[*\/]/') ){ |
2229
|
|
|
break; |
2230
|
|
|
} |
2231
|
|
|
|
2232
|
|
|
$op = $this->MatchChar('/'); |
2233
|
|
|
if( !$op ){ |
2234
|
|
|
$op = $this->MatchChar('*'); |
2235
|
|
|
if( !$op ){ |
2236
|
|
|
break; |
2237
|
|
|
} |
2238
|
|
|
} |
2239
|
|
|
|
2240
|
|
|
$a = $this->parseOperand(); |
2241
|
|
|
|
2242
|
|
|
if(!$a) { break; } |
2243
|
|
|
|
2244
|
|
|
$m->parensInOp = true; |
2245
|
|
|
$a->parensInOp = true; |
2246
|
|
|
$return = $this->NewObj3('Less_Tree_Operation',array( $op, array( $return, $a ), $isSpaced) ); |
2247
|
|
|
} |
2248
|
|
|
} |
2249
|
|
|
return $return; |
2250
|
|
|
|
2251
|
|
|
} |
2252
|
|
|
|
2253
|
|
|
|
2254
|
|
|
/** |
2255
|
|
|
* Parses an addition operation |
2256
|
|
|
* |
2257
|
|
|
* @return Less_Tree_Operation|null |
2258
|
|
|
*/ |
2259
|
|
|
private function parseAddition (){ |
2260
|
|
|
|
2261
|
|
|
$return = $m = $this->parseMultiplication(); |
2262
|
|
|
if( $return ){ |
2263
|
|
|
while( true ){ |
2264
|
|
|
|
2265
|
|
|
$isSpaced = $this->isWhitespace( -1 ); |
2266
|
|
|
|
2267
|
|
|
$op = $this->MatchReg('/\\G[-+]\s+/'); |
2268
|
|
|
if( $op ){ |
2269
|
|
|
$op = $op[0]; |
2270
|
|
|
}else{ |
2271
|
|
|
if( !$isSpaced ){ |
2272
|
|
|
$op = $this->match(array('#+','#-')); |
2273
|
|
|
} |
2274
|
|
|
if( !$op ){ |
2275
|
|
|
break; |
2276
|
|
|
} |
2277
|
|
|
} |
2278
|
|
|
|
2279
|
|
|
$a = $this->parseMultiplication(); |
2280
|
|
|
if( !$a ){ |
2281
|
|
|
break; |
2282
|
|
|
} |
2283
|
|
|
|
2284
|
|
|
$m->parensInOp = true; |
2285
|
|
|
$a->parensInOp = true; |
2286
|
|
|
$return = $this->NewObj3('Less_Tree_Operation',array($op, array($return, $a), $isSpaced)); |
2287
|
|
|
} |
2288
|
|
|
} |
2289
|
|
|
|
2290
|
|
|
return $return; |
2291
|
|
|
} |
2292
|
|
|
|
2293
|
|
|
|
2294
|
|
|
/** |
2295
|
|
|
* Parses the conditions |
2296
|
|
|
* |
2297
|
|
|
* @return Less_Tree_Condition|null |
2298
|
|
|
*/ |
2299
|
|
|
private function parseConditions() { |
2300
|
|
|
$index = $this->pos; |
2301
|
|
|
$return = $a = $this->parseCondition(); |
2302
|
|
|
if( $a ){ |
2303
|
|
|
while( true ){ |
2304
|
|
|
if( !$this->PeekReg('/\\G,\s*(not\s*)?\(/') || !$this->MatchChar(',') ){ |
2305
|
|
|
break; |
2306
|
|
|
} |
2307
|
|
|
$b = $this->parseCondition(); |
2308
|
|
|
if( !$b ){ |
2309
|
|
|
break; |
2310
|
|
|
} |
2311
|
|
|
|
2312
|
|
|
$return = $this->NewObj4('Less_Tree_Condition',array('or', $return, $b, $index)); |
2313
|
|
|
} |
2314
|
|
|
return $return; |
2315
|
|
|
} |
2316
|
|
|
} |
2317
|
|
|
|
2318
|
|
|
private function parseCondition() { |
2319
|
|
|
$index = $this->pos; |
2320
|
|
|
$negate = false; |
2321
|
|
|
$c = null; |
2322
|
|
|
|
2323
|
|
|
if ($this->MatchReg('/\\Gnot/')) $negate = true; |
2324
|
|
|
$this->expectChar('('); |
2325
|
|
|
$a = $this->MatchFuncs(array('parseAddition','parseEntitiesKeyword','parseEntitiesQuoted')); |
2326
|
|
|
|
2327
|
|
|
if( $a ){ |
2328
|
|
|
$op = $this->MatchReg('/\\G(?:>=|<=|=<|[<=>])/'); |
2329
|
|
|
if( $op ){ |
2330
|
|
|
$b = $this->MatchFuncs(array('parseAddition','parseEntitiesKeyword','parseEntitiesQuoted')); |
2331
|
|
View Code Duplication |
if( $b ){ |
2332
|
|
|
$c = $this->NewObj5('Less_Tree_Condition',array($op[0], $a, $b, $index, $negate)); |
2333
|
|
|
} else { |
2334
|
|
|
$this->Error('Unexpected expression'); |
2335
|
|
|
} |
2336
|
|
View Code Duplication |
} else { |
2337
|
|
|
$k = $this->NewObj1('Less_Tree_Keyword','true'); |
2338
|
|
|
$c = $this->NewObj5('Less_Tree_Condition',array('=', $a, $k, $index, $negate)); |
2339
|
|
|
} |
2340
|
|
|
$this->expectChar(')'); |
2341
|
|
|
return $this->MatchReg('/\\Gand/') ? $this->NewObj3('Less_Tree_Condition',array('and', $c, $this->parseCondition())) : $c; |
2342
|
|
|
} |
2343
|
|
|
} |
2344
|
|
|
|
2345
|
|
|
/** |
2346
|
|
|
* An operand is anything that can be part of an operation, |
2347
|
|
|
* such as a Color, or a Variable |
2348
|
|
|
* |
2349
|
|
|
*/ |
2350
|
|
|
private function parseOperand (){ |
2351
|
|
|
|
2352
|
|
|
$negate = false; |
2353
|
|
|
$offset = $this->pos+1; |
2354
|
|
|
if( $offset >= $this->input_len ){ |
2355
|
|
|
return; |
2356
|
|
|
} |
2357
|
|
|
$char = $this->input[$offset]; |
2358
|
|
|
if( $char === '@' || $char === '(' ){ |
2359
|
|
|
$negate = $this->MatchChar('-'); |
2360
|
|
|
} |
2361
|
|
|
|
2362
|
|
|
$o = $this->MatchFuncs(array('parseSub','parseEntitiesDimension','parseEntitiesColor','parseEntitiesVariable','parseEntitiesCall')); |
2363
|
|
|
|
2364
|
|
|
if( $negate ){ |
2365
|
|
|
$o->parensInOp = true; |
2366
|
|
|
$o = $this->NewObj1('Less_Tree_Negative',$o); |
2367
|
|
|
} |
2368
|
|
|
|
2369
|
|
|
return $o; |
2370
|
|
|
} |
2371
|
|
|
|
2372
|
|
|
|
2373
|
|
|
/** |
2374
|
|
|
* Expressions either represent mathematical operations, |
2375
|
|
|
* or white-space delimited Entities. |
2376
|
|
|
* |
2377
|
|
|
* 1px solid black |
2378
|
|
|
* @var * 2 |
2379
|
|
|
* |
2380
|
|
|
* @return Less_Tree_Expression|null |
2381
|
|
|
*/ |
2382
|
|
|
private function parseExpression (){ |
2383
|
|
|
$entities = array(); |
2384
|
|
|
|
2385
|
|
|
do{ |
2386
|
|
|
$e = $this->MatchFuncs(array('parseAddition','parseEntity')); |
2387
|
|
|
if( $e ){ |
2388
|
|
|
$entities[] = $e; |
2389
|
|
|
// operations do not allow keyword "/" dimension (e.g. small/20px) so we support that here |
2390
|
|
|
if( !$this->PeekReg('/\\G\/[\/*]/') ){ |
2391
|
|
|
$delim = $this->MatchChar('/'); |
2392
|
|
|
if( $delim ){ |
2393
|
|
|
$entities[] = $this->NewObj1('Less_Tree_Anonymous',$delim); |
2394
|
|
|
} |
2395
|
|
|
} |
2396
|
|
|
} |
2397
|
|
|
}while($e); |
2398
|
|
|
|
2399
|
|
|
if( $entities ){ |
|
|
|
|
2400
|
|
|
return $this->NewObj1('Less_Tree_Expression',$entities); |
2401
|
|
|
} |
2402
|
|
|
} |
2403
|
|
|
|
2404
|
|
|
|
2405
|
|
|
/** |
2406
|
|
|
* Parse a property |
2407
|
|
|
* eg: 'min-width', 'orientation', etc |
2408
|
|
|
* |
2409
|
|
|
* @return string |
2410
|
|
|
*/ |
2411
|
|
|
private function parseProperty (){ |
2412
|
|
|
$name = $this->MatchReg('/\\G(\*?-?[_a-zA-Z0-9-]+)\s*:/'); |
2413
|
|
|
if( $name ){ |
2414
|
|
|
return $name[1]; |
2415
|
|
|
} |
2416
|
|
|
} |
2417
|
|
|
|
2418
|
|
|
|
2419
|
|
|
/** |
2420
|
|
|
* Parse a rule property |
2421
|
|
|
* eg: 'color', 'width', 'height', etc |
2422
|
|
|
* |
2423
|
|
|
* @return string |
2424
|
|
|
*/ |
2425
|
|
|
private function parseRuleProperty(){ |
2426
|
|
|
$offset = $this->pos; |
2427
|
|
|
$name = array(); |
2428
|
|
|
$index = array(); |
2429
|
|
|
$length = 0; |
2430
|
|
|
|
2431
|
|
|
|
2432
|
|
|
$this->rulePropertyMatch('/\\G(\*?)/', $offset, $length, $index, $name ); |
2433
|
|
|
while( $this->rulePropertyMatch('/\\G((?:[\w-]+)|(?:@\{[\w-]+\}))/', $offset, $length, $index, $name )); // ! |
2434
|
|
|
|
2435
|
|
|
if( (count($name) > 1) && $this->rulePropertyMatch('/\\G\s*((?:\+_|\+)?)\s*:/', $offset, $length, $index, $name) ){ |
2436
|
|
|
// at last, we have the complete match now. move forward, |
2437
|
|
|
// convert name particles to tree objects and return: |
2438
|
|
|
$this->skipWhitespace($length); |
2439
|
|
|
|
2440
|
|
|
if( $name[0] === '' ){ |
2441
|
|
|
array_shift($name); |
2442
|
|
|
array_shift($index); |
2443
|
|
|
} |
2444
|
|
|
foreach($name as $k => $s ){ |
2445
|
|
|
if( !$s || $s[0] !== '@' ){ |
2446
|
|
|
$name[$k] = $this->NewObj1('Less_Tree_Keyword',$s); |
2447
|
|
|
}else{ |
2448
|
|
|
$name[$k] = $this->NewObj3('Less_Tree_Variable',array('@' . substr($s,2,-1), $index[$k], $this->env->currentFileInfo)); |
2449
|
|
|
} |
2450
|
|
|
} |
2451
|
|
|
return $name; |
2452
|
|
|
} |
2453
|
|
|
|
2454
|
|
|
|
2455
|
|
|
} |
2456
|
|
|
|
2457
|
|
|
private function rulePropertyMatch( $re, &$offset, &$length, &$index, &$name ){ |
2458
|
|
|
preg_match($re, $this->input, $a, 0, $offset); |
2459
|
|
|
if( $a ){ |
|
|
|
|
2460
|
|
|
$index[] = $this->pos + $length; |
2461
|
|
|
$length += strlen($a[0]); |
2462
|
|
|
$offset += strlen($a[0]); |
2463
|
|
|
$name[] = $a[1]; |
2464
|
|
|
return true; |
2465
|
|
|
} |
2466
|
|
|
} |
2467
|
|
|
|
2468
|
|
|
public static function serializeVars( $vars ){ |
2469
|
|
|
$s = ''; |
2470
|
|
|
|
2471
|
|
|
foreach($vars as $name => $value){ |
2472
|
|
|
$s .= (($name[0] === '@') ? '' : '@') . $name .': '. $value . ((substr($value,-1) === ';') ? '' : ';'); |
2473
|
|
|
} |
2474
|
|
|
|
2475
|
|
|
return $s; |
2476
|
|
|
} |
2477
|
|
|
|
2478
|
|
|
|
2479
|
|
|
/** |
2480
|
|
|
* Some versions of php have trouble with method_exists($a,$b) if $a is not an object |
2481
|
|
|
* |
2482
|
|
|
* @param string $b |
2483
|
|
|
*/ |
2484
|
|
|
public static function is_method($a,$b){ |
2485
|
|
|
return is_object($a) && method_exists($a,$b); |
2486
|
|
|
} |
2487
|
|
|
|
2488
|
|
|
|
2489
|
|
|
/** |
2490
|
|
|
* Round numbers similarly to javascript |
2491
|
|
|
* eg: 1.499999 to 1 instead of 2 |
2492
|
|
|
* |
2493
|
|
|
*/ |
2494
|
|
|
public static function round($i, $precision = 0){ |
2495
|
|
|
|
2496
|
|
|
$precision = pow(10,$precision); |
2497
|
|
|
$i = $i*$precision; |
2498
|
|
|
|
2499
|
|
|
$ceil = ceil($i); |
2500
|
|
|
$floor = floor($i); |
2501
|
|
|
if( ($ceil - $i) <= ($i - $floor) ){ |
2502
|
|
|
return $ceil/$precision; |
2503
|
|
|
}else{ |
2504
|
|
|
return $floor/$precision; |
2505
|
|
|
} |
2506
|
|
|
} |
2507
|
|
|
|
2508
|
|
|
|
2509
|
|
|
/** |
2510
|
|
|
* Create Less_Tree_* objects and optionally generate a cache string |
2511
|
|
|
* |
2512
|
|
|
* @return mixed |
2513
|
|
|
*/ |
2514
|
|
|
public function NewObj0($class){ |
2515
|
|
|
$obj = new $class(); |
2516
|
|
|
if( $this->CacheEnabled() ){ |
2517
|
|
|
$obj->cache_string = ' new '.$class.'()'; |
2518
|
|
|
} |
2519
|
|
|
return $obj; |
2520
|
|
|
} |
2521
|
|
|
|
2522
|
|
|
public function NewObj1($class, $arg){ |
2523
|
|
|
$obj = new $class( $arg ); |
2524
|
|
|
if( $this->CacheEnabled() ){ |
2525
|
|
|
$obj->cache_string = ' new '.$class.'('.Less_Parser::ArgString($arg).')'; |
2526
|
|
|
} |
2527
|
|
|
return $obj; |
2528
|
|
|
} |
2529
|
|
|
|
2530
|
|
|
public function NewObj2($class, $args){ |
2531
|
|
|
$obj = new $class( $args[0], $args[1] ); |
2532
|
|
|
if( $this->CacheEnabled() ){ |
2533
|
|
|
$this->ObjCache( $obj, $class, $args); |
2534
|
|
|
} |
2535
|
|
|
return $obj; |
2536
|
|
|
} |
2537
|
|
|
|
2538
|
|
|
public function NewObj3($class, $args){ |
2539
|
|
|
$obj = new $class( $args[0], $args[1], $args[2] ); |
2540
|
|
|
if( $this->CacheEnabled() ){ |
2541
|
|
|
$this->ObjCache( $obj, $class, $args); |
2542
|
|
|
} |
2543
|
|
|
return $obj; |
2544
|
|
|
} |
2545
|
|
|
|
2546
|
|
|
public function NewObj4($class, $args){ |
2547
|
|
|
$obj = new $class( $args[0], $args[1], $args[2], $args[3] ); |
2548
|
|
|
if( $this->CacheEnabled() ){ |
2549
|
|
|
$this->ObjCache( $obj, $class, $args); |
2550
|
|
|
} |
2551
|
|
|
return $obj; |
2552
|
|
|
} |
2553
|
|
|
|
2554
|
|
|
public function NewObj5($class, $args){ |
2555
|
|
|
$obj = new $class( $args[0], $args[1], $args[2], $args[3], $args[4] ); |
2556
|
|
|
if( $this->CacheEnabled() ){ |
2557
|
|
|
$this->ObjCache( $obj, $class, $args); |
2558
|
|
|
} |
2559
|
|
|
return $obj; |
2560
|
|
|
} |
2561
|
|
|
|
2562
|
|
View Code Duplication |
public function NewObj6($class, $args){ |
2563
|
|
|
$obj = new $class( $args[0], $args[1], $args[2], $args[3], $args[4], $args[5] ); |
2564
|
|
|
if( $this->CacheEnabled() ){ |
2565
|
|
|
$this->ObjCache( $obj, $class, $args); |
2566
|
|
|
} |
2567
|
|
|
return $obj; |
2568
|
|
|
} |
2569
|
|
|
|
2570
|
|
View Code Duplication |
public function NewObj7($class, $args){ |
2571
|
|
|
$obj = new $class( $args[0], $args[1], $args[2], $args[3], $args[4], $args[5], $args[6] ); |
2572
|
|
|
if( $this->CacheEnabled() ){ |
2573
|
|
|
$this->ObjCache( $obj, $class, $args); |
2574
|
|
|
} |
2575
|
|
|
return $obj; |
2576
|
|
|
} |
2577
|
|
|
|
2578
|
|
|
//caching |
2579
|
|
|
public function ObjCache($obj, $class, $args=array()){ |
2580
|
|
|
$obj->cache_string = ' new '.$class.'('. self::ArgCache($args).')'; |
2581
|
|
|
} |
2582
|
|
|
|
2583
|
|
|
public function ArgCache($args){ |
2584
|
|
|
return implode(',',array_map( array('Less_Parser','ArgString'),$args)); |
2585
|
|
|
} |
2586
|
|
|
|
2587
|
|
|
|
2588
|
|
|
/** |
2589
|
|
|
* Convert an argument to a string for use in the parser cache |
2590
|
|
|
* |
2591
|
|
|
* @return string |
2592
|
|
|
*/ |
2593
|
|
|
public static function ArgString($arg){ |
2594
|
|
|
|
2595
|
|
|
$type = gettype($arg); |
2596
|
|
|
|
2597
|
|
|
if( $type === 'object'){ |
2598
|
|
|
$string = $arg->cache_string; |
2599
|
|
|
unset($arg->cache_string); |
2600
|
|
|
return $string; |
2601
|
|
|
|
2602
|
|
|
}elseif( $type === 'array' ){ |
2603
|
|
|
$string = ' Array('; |
2604
|
|
|
foreach($arg as $k => $a){ |
2605
|
|
|
$string .= var_export($k,true).' => '.self::ArgString($a).','; |
2606
|
|
|
} |
2607
|
|
|
return $string . ')'; |
2608
|
|
|
} |
2609
|
|
|
|
2610
|
|
|
return var_export($arg,true); |
2611
|
|
|
} |
2612
|
|
|
|
2613
|
|
|
public function Error($msg){ |
2614
|
|
|
throw new Less_Exception_Parser($msg, null, $this->furthest, $this->env->currentFileInfo); |
2615
|
|
|
} |
2616
|
|
|
|
2617
|
|
|
public static function WinPath($path){ |
2618
|
|
|
return str_replace('\\', '/', $path); |
2619
|
|
|
} |
2620
|
|
|
|
2621
|
|
|
public function CacheEnabled(){ |
2622
|
|
|
return (Less_Parser::$options['cache_method'] && (Less_Cache::$cache_dir || (Less_Parser::$options['cache_method'] == 'callback'))); |
2623
|
|
|
} |
2624
|
|
|
|
2625
|
|
|
} |
2626
|
|
|
|
2627
|
|
|
|
2628
|
|
|
|
2629
|
|
|
|
2630
|
|
|
/** |
2631
|
|
|
* Utility for css colors |
2632
|
|
|
* |
2633
|
|
|
* @package Less |
2634
|
|
|
* @subpackage color |
2635
|
|
|
*/ |
2636
|
|
|
class Less_Colors { |
2637
|
|
|
|
2638
|
|
|
public static $colors = array( |
2639
|
|
|
'aliceblue'=>'#f0f8ff', |
2640
|
|
|
'antiquewhite'=>'#faebd7', |
2641
|
|
|
'aqua'=>'#00ffff', |
2642
|
|
|
'aquamarine'=>'#7fffd4', |
2643
|
|
|
'azure'=>'#f0ffff', |
2644
|
|
|
'beige'=>'#f5f5dc', |
2645
|
|
|
'bisque'=>'#ffe4c4', |
2646
|
|
|
'black'=>'#000000', |
2647
|
|
|
'blanchedalmond'=>'#ffebcd', |
2648
|
|
|
'blue'=>'#0000ff', |
2649
|
|
|
'blueviolet'=>'#8a2be2', |
2650
|
|
|
'brown'=>'#a52a2a', |
2651
|
|
|
'burlywood'=>'#deb887', |
2652
|
|
|
'cadetblue'=>'#5f9ea0', |
2653
|
|
|
'chartreuse'=>'#7fff00', |
2654
|
|
|
'chocolate'=>'#d2691e', |
2655
|
|
|
'coral'=>'#ff7f50', |
2656
|
|
|
'cornflowerblue'=>'#6495ed', |
2657
|
|
|
'cornsilk'=>'#fff8dc', |
2658
|
|
|
'crimson'=>'#dc143c', |
2659
|
|
|
'cyan'=>'#00ffff', |
2660
|
|
|
'darkblue'=>'#00008b', |
2661
|
|
|
'darkcyan'=>'#008b8b', |
2662
|
|
|
'darkgoldenrod'=>'#b8860b', |
2663
|
|
|
'darkgray'=>'#a9a9a9', |
2664
|
|
|
'darkgrey'=>'#a9a9a9', |
2665
|
|
|
'darkgreen'=>'#006400', |
2666
|
|
|
'darkkhaki'=>'#bdb76b', |
2667
|
|
|
'darkmagenta'=>'#8b008b', |
2668
|
|
|
'darkolivegreen'=>'#556b2f', |
2669
|
|
|
'darkorange'=>'#ff8c00', |
2670
|
|
|
'darkorchid'=>'#9932cc', |
2671
|
|
|
'darkred'=>'#8b0000', |
2672
|
|
|
'darksalmon'=>'#e9967a', |
2673
|
|
|
'darkseagreen'=>'#8fbc8f', |
2674
|
|
|
'darkslateblue'=>'#483d8b', |
2675
|
|
|
'darkslategray'=>'#2f4f4f', |
2676
|
|
|
'darkslategrey'=>'#2f4f4f', |
2677
|
|
|
'darkturquoise'=>'#00ced1', |
2678
|
|
|
'darkviolet'=>'#9400d3', |
2679
|
|
|
'deeppink'=>'#ff1493', |
2680
|
|
|
'deepskyblue'=>'#00bfff', |
2681
|
|
|
'dimgray'=>'#696969', |
2682
|
|
|
'dimgrey'=>'#696969', |
2683
|
|
|
'dodgerblue'=>'#1e90ff', |
2684
|
|
|
'firebrick'=>'#b22222', |
2685
|
|
|
'floralwhite'=>'#fffaf0', |
2686
|
|
|
'forestgreen'=>'#228b22', |
2687
|
|
|
'fuchsia'=>'#ff00ff', |
2688
|
|
|
'gainsboro'=>'#dcdcdc', |
2689
|
|
|
'ghostwhite'=>'#f8f8ff', |
2690
|
|
|
'gold'=>'#ffd700', |
2691
|
|
|
'goldenrod'=>'#daa520', |
2692
|
|
|
'gray'=>'#808080', |
2693
|
|
|
'grey'=>'#808080', |
2694
|
|
|
'green'=>'#008000', |
2695
|
|
|
'greenyellow'=>'#adff2f', |
2696
|
|
|
'honeydew'=>'#f0fff0', |
2697
|
|
|
'hotpink'=>'#ff69b4', |
2698
|
|
|
'indianred'=>'#cd5c5c', |
2699
|
|
|
'indigo'=>'#4b0082', |
2700
|
|
|
'ivory'=>'#fffff0', |
2701
|
|
|
'khaki'=>'#f0e68c', |
2702
|
|
|
'lavender'=>'#e6e6fa', |
2703
|
|
|
'lavenderblush'=>'#fff0f5', |
2704
|
|
|
'lawngreen'=>'#7cfc00', |
2705
|
|
|
'lemonchiffon'=>'#fffacd', |
2706
|
|
|
'lightblue'=>'#add8e6', |
2707
|
|
|
'lightcoral'=>'#f08080', |
2708
|
|
|
'lightcyan'=>'#e0ffff', |
2709
|
|
|
'lightgoldenrodyellow'=>'#fafad2', |
2710
|
|
|
'lightgray'=>'#d3d3d3', |
2711
|
|
|
'lightgrey'=>'#d3d3d3', |
2712
|
|
|
'lightgreen'=>'#90ee90', |
2713
|
|
|
'lightpink'=>'#ffb6c1', |
2714
|
|
|
'lightsalmon'=>'#ffa07a', |
2715
|
|
|
'lightseagreen'=>'#20b2aa', |
2716
|
|
|
'lightskyblue'=>'#87cefa', |
2717
|
|
|
'lightslategray'=>'#778899', |
2718
|
|
|
'lightslategrey'=>'#778899', |
2719
|
|
|
'lightsteelblue'=>'#b0c4de', |
2720
|
|
|
'lightyellow'=>'#ffffe0', |
2721
|
|
|
'lime'=>'#00ff00', |
2722
|
|
|
'limegreen'=>'#32cd32', |
2723
|
|
|
'linen'=>'#faf0e6', |
2724
|
|
|
'magenta'=>'#ff00ff', |
2725
|
|
|
'maroon'=>'#800000', |
2726
|
|
|
'mediumaquamarine'=>'#66cdaa', |
2727
|
|
|
'mediumblue'=>'#0000cd', |
2728
|
|
|
'mediumorchid'=>'#ba55d3', |
2729
|
|
|
'mediumpurple'=>'#9370d8', |
2730
|
|
|
'mediumseagreen'=>'#3cb371', |
2731
|
|
|
'mediumslateblue'=>'#7b68ee', |
2732
|
|
|
'mediumspringgreen'=>'#00fa9a', |
2733
|
|
|
'mediumturquoise'=>'#48d1cc', |
2734
|
|
|
'mediumvioletred'=>'#c71585', |
2735
|
|
|
'midnightblue'=>'#191970', |
2736
|
|
|
'mintcream'=>'#f5fffa', |
2737
|
|
|
'mistyrose'=>'#ffe4e1', |
2738
|
|
|
'moccasin'=>'#ffe4b5', |
2739
|
|
|
'navajowhite'=>'#ffdead', |
2740
|
|
|
'navy'=>'#000080', |
2741
|
|
|
'oldlace'=>'#fdf5e6', |
2742
|
|
|
'olive'=>'#808000', |
2743
|
|
|
'olivedrab'=>'#6b8e23', |
2744
|
|
|
'orange'=>'#ffa500', |
2745
|
|
|
'orangered'=>'#ff4500', |
2746
|
|
|
'orchid'=>'#da70d6', |
2747
|
|
|
'palegoldenrod'=>'#eee8aa', |
2748
|
|
|
'palegreen'=>'#98fb98', |
2749
|
|
|
'paleturquoise'=>'#afeeee', |
2750
|
|
|
'palevioletred'=>'#d87093', |
2751
|
|
|
'papayawhip'=>'#ffefd5', |
2752
|
|
|
'peachpuff'=>'#ffdab9', |
2753
|
|
|
'peru'=>'#cd853f', |
2754
|
|
|
'pink'=>'#ffc0cb', |
2755
|
|
|
'plum'=>'#dda0dd', |
2756
|
|
|
'powderblue'=>'#b0e0e6', |
2757
|
|
|
'purple'=>'#800080', |
2758
|
|
|
'red'=>'#ff0000', |
2759
|
|
|
'rosybrown'=>'#bc8f8f', |
2760
|
|
|
'royalblue'=>'#4169e1', |
2761
|
|
|
'saddlebrown'=>'#8b4513', |
2762
|
|
|
'salmon'=>'#fa8072', |
2763
|
|
|
'sandybrown'=>'#f4a460', |
2764
|
|
|
'seagreen'=>'#2e8b57', |
2765
|
|
|
'seashell'=>'#fff5ee', |
2766
|
|
|
'sienna'=>'#a0522d', |
2767
|
|
|
'silver'=>'#c0c0c0', |
2768
|
|
|
'skyblue'=>'#87ceeb', |
2769
|
|
|
'slateblue'=>'#6a5acd', |
2770
|
|
|
'slategray'=>'#708090', |
2771
|
|
|
'slategrey'=>'#708090', |
2772
|
|
|
'snow'=>'#fffafa', |
2773
|
|
|
'springgreen'=>'#00ff7f', |
2774
|
|
|
'steelblue'=>'#4682b4', |
2775
|
|
|
'tan'=>'#d2b48c', |
2776
|
|
|
'teal'=>'#008080', |
2777
|
|
|
'thistle'=>'#d8bfd8', |
2778
|
|
|
'tomato'=>'#ff6347', |
2779
|
|
|
'turquoise'=>'#40e0d0', |
2780
|
|
|
'violet'=>'#ee82ee', |
2781
|
|
|
'wheat'=>'#f5deb3', |
2782
|
|
|
'white'=>'#ffffff', |
2783
|
|
|
'whitesmoke'=>'#f5f5f5', |
2784
|
|
|
'yellow'=>'#ffff00', |
2785
|
|
|
'yellowgreen'=>'#9acd32' |
2786
|
|
|
); |
2787
|
|
|
|
2788
|
|
|
public static function hasOwnProperty($color) { |
2789
|
|
|
return isset(self::$colors[$color]); |
2790
|
|
|
} |
2791
|
|
|
|
2792
|
|
|
|
2793
|
|
|
public static function color($color) { |
2794
|
|
|
return self::$colors[$color]; |
2795
|
|
|
} |
2796
|
|
|
|
2797
|
|
|
} |
2798
|
|
|
|
2799
|
|
|
|
2800
|
|
|
|
2801
|
|
|
/** |
2802
|
|
|
* Environment |
2803
|
|
|
* |
2804
|
|
|
* @package Less |
2805
|
|
|
* @subpackage environment |
2806
|
|
|
*/ |
2807
|
|
|
class Less_Environment{ |
2808
|
|
|
|
2809
|
|
|
//public $paths = array(); // option - unmodified - paths to search for imports on |
2810
|
|
|
//public static $files = array(); // list of files that have been imported, used for import-once |
2811
|
|
|
//public $rootpath; // option - rootpath to append to URL's |
2812
|
|
|
//public static $strictImports = null; // option - |
2813
|
|
|
//public $insecure; // option - whether to allow imports from insecure ssl hosts |
2814
|
|
|
//public $processImports; // option - whether to process imports. if false then imports will not be imported |
2815
|
|
|
//public $javascriptEnabled; // option - whether JavaScript is enabled. if undefined, defaults to true |
2816
|
|
|
//public $useFileCache; // browser only - whether to use the per file session cache |
2817
|
|
|
public $currentFileInfo; // information about the current file - for error reporting and importing and making urls relative etc. |
2818
|
|
|
|
2819
|
|
|
public $importMultiple = false; // whether we are currently importing multiple copies |
2820
|
|
|
|
2821
|
|
|
|
2822
|
|
|
/** |
2823
|
|
|
* @var array |
2824
|
|
|
*/ |
2825
|
|
|
public $frames = array(); |
2826
|
|
|
|
2827
|
|
|
/** |
2828
|
|
|
* @var array |
2829
|
|
|
*/ |
2830
|
|
|
public $mediaBlocks = array(); |
2831
|
|
|
|
2832
|
|
|
/** |
2833
|
|
|
* @var array |
2834
|
|
|
*/ |
2835
|
|
|
public $mediaPath = array(); |
2836
|
|
|
|
2837
|
|
|
public static $parensStack = 0; |
2838
|
|
|
|
2839
|
|
|
public static $tabLevel = 0; |
2840
|
|
|
|
2841
|
|
|
public static $lastRule = false; |
2842
|
|
|
|
2843
|
|
|
public static $_outputMap; |
2844
|
|
|
|
2845
|
|
|
public static $mixin_stack = 0; |
2846
|
|
|
|
2847
|
|
|
/** |
2848
|
|
|
* @var array |
2849
|
|
|
*/ |
2850
|
|
|
public $functions = array(); |
2851
|
|
|
|
2852
|
|
|
|
2853
|
|
|
public function Init(){ |
2854
|
|
|
|
2855
|
|
|
self::$parensStack = 0; |
2856
|
|
|
self::$tabLevel = 0; |
2857
|
|
|
self::$lastRule = false; |
2858
|
|
|
self::$mixin_stack = 0; |
2859
|
|
|
|
2860
|
|
|
if( Less_Parser::$options['compress'] ){ |
2861
|
|
|
|
2862
|
|
|
Less_Environment::$_outputMap = array( |
2863
|
|
|
',' => ',', |
2864
|
|
|
': ' => ':', |
2865
|
|
|
'' => '', |
2866
|
|
|
' ' => ' ', |
2867
|
|
|
':' => ' :', |
2868
|
|
|
'+' => '+', |
2869
|
|
|
'~' => '~', |
2870
|
|
|
'>' => '>', |
2871
|
|
|
'|' => '|', |
2872
|
|
|
'^' => '^', |
2873
|
|
|
'^^' => '^^' |
2874
|
|
|
); |
2875
|
|
|
|
2876
|
|
|
}else{ |
2877
|
|
|
|
2878
|
|
|
Less_Environment::$_outputMap = array( |
2879
|
|
|
',' => ', ', |
2880
|
|
|
': ' => ': ', |
2881
|
|
|
'' => '', |
2882
|
|
|
' ' => ' ', |
2883
|
|
|
':' => ' :', |
2884
|
|
|
'+' => ' + ', |
2885
|
|
|
'~' => ' ~ ', |
2886
|
|
|
'>' => ' > ', |
2887
|
|
|
'|' => '|', |
2888
|
|
|
'^' => ' ^ ', |
2889
|
|
|
'^^' => ' ^^ ' |
2890
|
|
|
); |
2891
|
|
|
|
2892
|
|
|
} |
2893
|
|
|
} |
2894
|
|
|
|
2895
|
|
|
|
2896
|
|
|
public function copyEvalEnv($frames = array() ){ |
2897
|
|
|
$new_env = new Less_Environment(); |
2898
|
|
|
$new_env->frames = $frames; |
2899
|
|
|
return $new_env; |
2900
|
|
|
} |
2901
|
|
|
|
2902
|
|
|
|
2903
|
|
|
public static function isMathOn(){ |
2904
|
|
|
return !Less_Parser::$options['strictMath'] || Less_Environment::$parensStack; |
2905
|
|
|
} |
2906
|
|
|
|
2907
|
|
|
public static function isPathRelative($path){ |
2908
|
|
|
return !preg_match('/^(?:[a-z-]+:|\/)/',$path); |
2909
|
|
|
} |
2910
|
|
|
|
2911
|
|
|
|
2912
|
|
|
/** |
2913
|
|
|
* Canonicalize a path by resolving references to '/./', '/../' |
2914
|
|
|
* Does not remove leading "../" |
2915
|
|
|
* @param string path or url |
2916
|
|
|
* @return string Canonicalized path |
2917
|
|
|
* |
2918
|
|
|
*/ |
2919
|
|
|
public static function normalizePath($path){ |
2920
|
|
|
|
2921
|
|
|
$segments = explode('/',$path); |
2922
|
|
|
$segments = array_reverse($segments); |
2923
|
|
|
|
2924
|
|
|
$path = array(); |
2925
|
|
|
$path_len = 0; |
2926
|
|
|
|
2927
|
|
|
while( $segments ){ |
|
|
|
|
2928
|
|
|
$segment = array_pop($segments); |
2929
|
|
|
switch( $segment ) { |
2930
|
|
|
|
2931
|
|
|
case '.': |
2932
|
|
|
break; |
2933
|
|
|
|
2934
|
|
|
case '..': |
2935
|
|
|
if( !$path_len || ( $path[$path_len-1] === '..') ){ |
2936
|
|
|
$path[] = $segment; |
2937
|
|
|
$path_len++; |
2938
|
|
|
}else{ |
2939
|
|
|
array_pop($path); |
2940
|
|
|
$path_len--; |
2941
|
|
|
} |
2942
|
|
|
break; |
2943
|
|
|
|
2944
|
|
|
default: |
2945
|
|
|
$path[] = $segment; |
2946
|
|
|
$path_len++; |
2947
|
|
|
break; |
2948
|
|
|
} |
2949
|
|
|
} |
2950
|
|
|
|
2951
|
|
|
return implode('/',$path); |
2952
|
|
|
} |
2953
|
|
|
|
2954
|
|
|
|
2955
|
|
|
public function unshiftFrame($frame){ |
2956
|
|
|
array_unshift($this->frames, $frame); |
2957
|
|
|
} |
2958
|
|
|
|
2959
|
|
|
public function shiftFrame(){ |
2960
|
|
|
return array_shift($this->frames); |
2961
|
|
|
} |
2962
|
|
|
|
2963
|
|
|
} |
2964
|
|
|
|
2965
|
|
|
|
2966
|
|
|
/** |
2967
|
|
|
* Builtin functions |
2968
|
|
|
* |
2969
|
|
|
* @package Less |
2970
|
|
|
* @subpackage function |
2971
|
|
|
* @see http://lesscss.org/functions/ |
2972
|
|
|
*/ |
2973
|
|
|
class Less_Functions{ |
2974
|
|
|
|
2975
|
|
|
public $env; |
2976
|
|
|
public $currentFileInfo; |
2977
|
|
|
|
2978
|
|
|
function __construct($env, $currentFileInfo = null ){ |
|
|
|
|
2979
|
|
|
$this->env = $env; |
2980
|
|
|
$this->currentFileInfo = $currentFileInfo; |
2981
|
|
|
} |
2982
|
|
|
|
2983
|
|
|
/** |
2984
|
|
|
* @param string $op |
2985
|
|
|
*/ |
2986
|
|
|
public static function operate( $op, $a, $b ){ |
2987
|
|
|
switch ($op) { |
2988
|
|
|
case '+': return $a + $b; |
2989
|
|
|
case '-': return $a - $b; |
2990
|
|
|
case '*': return $a * $b; |
2991
|
|
|
case '/': return $a / $b; |
2992
|
|
|
} |
2993
|
|
|
} |
2994
|
|
|
|
2995
|
|
|
public static function clamp($val, $max = 1){ |
2996
|
|
|
return min( max($val, 0), $max); |
2997
|
|
|
} |
2998
|
|
|
|
2999
|
|
|
public static function fround( $value ){ |
3000
|
|
|
|
3001
|
|
|
if( $value === 0 ){ |
3002
|
|
|
return $value; |
3003
|
|
|
} |
3004
|
|
|
|
3005
|
|
|
if( Less_Parser::$options['numPrecision'] ){ |
3006
|
|
|
$p = pow(10, Less_Parser::$options['numPrecision']); |
3007
|
|
|
return round( $value * $p) / $p; |
3008
|
|
|
} |
3009
|
|
|
return $value; |
3010
|
|
|
} |
3011
|
|
|
|
3012
|
|
|
public static function number($n){ |
3013
|
|
|
|
3014
|
|
|
if ($n instanceof Less_Tree_Dimension) { |
3015
|
|
|
return floatval( $n->unit->is('%') ? $n->value / 100 : $n->value); |
3016
|
|
|
} else if (is_numeric($n)) { |
3017
|
|
|
return $n; |
3018
|
|
|
} else { |
3019
|
|
|
throw new Less_Exception_Compiler("color functions take numbers as parameters"); |
3020
|
|
|
} |
3021
|
|
|
} |
3022
|
|
|
|
3023
|
|
|
public static function scaled($n, $size = 255 ){ |
3024
|
|
|
if( $n instanceof Less_Tree_Dimension && $n->unit->is('%') ){ |
3025
|
|
|
return (float)$n->value * $size / 100; |
3026
|
|
|
} else { |
3027
|
|
|
return Less_Functions::number($n); |
3028
|
|
|
} |
3029
|
|
|
} |
3030
|
|
|
|
3031
|
|
|
public function rgb ($r = null, $g = null, $b = null){ |
3032
|
|
|
if (is_null($r) || is_null($g) || is_null($b)) { |
3033
|
|
|
throw new Less_Exception_Compiler("rgb expects three parameters"); |
3034
|
|
|
} |
3035
|
|
|
return $this->rgba($r, $g, $b, 1.0); |
3036
|
|
|
} |
3037
|
|
|
|
3038
|
|
|
public function rgba($r = null, $g = null, $b = null, $a = null){ |
3039
|
|
|
$rgb = array($r, $g, $b); |
3040
|
|
|
$rgb = array_map(array('Less_Functions','scaled'),$rgb); |
3041
|
|
|
|
3042
|
|
|
$a = self::number($a); |
3043
|
|
|
return new Less_Tree_Color($rgb, $a); |
3044
|
|
|
} |
3045
|
|
|
|
3046
|
|
|
public function hsl($h, $s, $l){ |
3047
|
|
|
return $this->hsla($h, $s, $l, 1.0); |
3048
|
|
|
} |
3049
|
|
|
|
3050
|
|
|
public function hsla($h, $s, $l, $a){ |
3051
|
|
|
|
3052
|
|
|
$h = fmod(self::number($h), 360) / 360; // Classic % operator will change float to int |
3053
|
|
|
$s = self::clamp(self::number($s)); |
3054
|
|
|
$l = self::clamp(self::number($l)); |
3055
|
|
|
$a = self::clamp(self::number($a)); |
3056
|
|
|
|
3057
|
|
|
$m2 = $l <= 0.5 ? $l * ($s + 1) : $l + $s - $l * $s; |
3058
|
|
|
|
3059
|
|
|
$m1 = $l * 2 - $m2; |
3060
|
|
|
|
3061
|
|
|
return $this->rgba( self::hsla_hue($h + 1/3, $m1, $m2) * 255, |
3062
|
|
|
self::hsla_hue($h, $m1, $m2) * 255, |
3063
|
|
|
self::hsla_hue($h - 1/3, $m1, $m2) * 255, |
3064
|
|
|
$a); |
3065
|
|
|
} |
3066
|
|
|
|
3067
|
|
|
/** |
3068
|
|
|
* @param double $h |
3069
|
|
|
*/ |
3070
|
|
|
public function hsla_hue($h, $m1, $m2){ |
3071
|
|
|
$h = $h < 0 ? $h + 1 : ($h > 1 ? $h - 1 : $h); |
3072
|
|
|
if ($h * 6 < 1) return $m1 + ($m2 - $m1) * $h * 6; |
3073
|
|
|
else if ($h * 2 < 1) return $m2; |
3074
|
|
|
else if ($h * 3 < 2) return $m1 + ($m2 - $m1) * (2/3 - $h) * 6; |
3075
|
|
|
else return $m1; |
3076
|
|
|
} |
3077
|
|
|
|
3078
|
|
|
public function hsv($h, $s, $v) { |
3079
|
|
|
return $this->hsva($h, $s, $v, 1.0); |
3080
|
|
|
} |
3081
|
|
|
|
3082
|
|
|
/** |
3083
|
|
|
* @param double $a |
3084
|
|
|
*/ |
3085
|
|
|
public function hsva($h, $s, $v, $a) { |
3086
|
|
|
$h = ((Less_Functions::number($h) % 360) / 360 ) * 360; |
3087
|
|
|
$s = Less_Functions::number($s); |
3088
|
|
|
$v = Less_Functions::number($v); |
3089
|
|
|
$a = Less_Functions::number($a); |
3090
|
|
|
|
3091
|
|
|
$i = floor(($h / 60) % 6); |
3092
|
|
|
$f = ($h / 60) - $i; |
3093
|
|
|
|
3094
|
|
|
$vs = array( $v, |
3095
|
|
|
$v * (1 - $s), |
3096
|
|
|
$v * (1 - $f * $s), |
3097
|
|
|
$v * (1 - (1 - $f) * $s)); |
3098
|
|
|
|
3099
|
|
|
$perm = array(array(0, 3, 1), |
3100
|
|
|
array(2, 0, 1), |
3101
|
|
|
array(1, 0, 3), |
3102
|
|
|
array(1, 2, 0), |
3103
|
|
|
array(3, 1, 0), |
3104
|
|
|
array(0, 1, 2)); |
3105
|
|
|
|
3106
|
|
|
return $this->rgba($vs[$perm[$i][0]] * 255, |
3107
|
|
|
$vs[$perm[$i][1]] * 255, |
3108
|
|
|
$vs[$perm[$i][2]] * 255, |
3109
|
|
|
$a); |
3110
|
|
|
} |
3111
|
|
|
|
3112
|
|
|
public function hue($color = null){ |
3113
|
|
|
if (!$color instanceof Less_Tree_Color) { |
3114
|
|
|
throw new Less_Exception_Compiler('The first argument to hue must be a color' . ($color instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '') ); |
3115
|
|
|
} |
3116
|
|
|
|
3117
|
|
|
$c = $color->toHSL(); |
3118
|
|
|
return new Less_Tree_Dimension(Less_Parser::round($c['h'])); |
3119
|
|
|
} |
3120
|
|
|
|
3121
|
|
View Code Duplication |
public function saturation($color = null){ |
3122
|
|
|
if (!$color instanceof Less_Tree_Color) { |
3123
|
|
|
throw new Less_Exception_Compiler('The first argument to saturation must be a color' . ($color instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '') ); |
3124
|
|
|
} |
3125
|
|
|
|
3126
|
|
|
$c = $color->toHSL(); |
3127
|
|
|
return new Less_Tree_Dimension(Less_Parser::round($c['s'] * 100), '%'); |
3128
|
|
|
} |
3129
|
|
|
|
3130
|
|
View Code Duplication |
public function lightness($color = null){ |
3131
|
|
|
if (!$color instanceof Less_Tree_Color) { |
3132
|
|
|
throw new Less_Exception_Compiler('The first argument to lightness must be a color' . ($color instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '') ); |
3133
|
|
|
} |
3134
|
|
|
|
3135
|
|
|
$c = $color->toHSL(); |
3136
|
|
|
return new Less_Tree_Dimension(Less_Parser::round($c['l'] * 100), '%'); |
3137
|
|
|
} |
3138
|
|
|
|
3139
|
|
|
public function hsvhue( $color = null ){ |
3140
|
|
|
if (!$color instanceof Less_Tree_Color) { |
3141
|
|
|
throw new Less_Exception_Compiler('The first argument to hsvhue must be a color' . ($color instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '') ); |
3142
|
|
|
} |
3143
|
|
|
|
3144
|
|
|
$hsv = $color->toHSV(); |
3145
|
|
|
return new Less_Tree_Dimension( Less_Parser::round($hsv['h']) ); |
3146
|
|
|
} |
3147
|
|
|
|
3148
|
|
|
|
3149
|
|
View Code Duplication |
public function hsvsaturation( $color = null ){ |
3150
|
|
|
if (!$color instanceof Less_Tree_Color) { |
3151
|
|
|
throw new Less_Exception_Compiler('The first argument to hsvsaturation must be a color' . ($color instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '') ); |
3152
|
|
|
} |
3153
|
|
|
|
3154
|
|
|
$hsv = $color->toHSV(); |
3155
|
|
|
return new Less_Tree_Dimension( Less_Parser::round($hsv['s'] * 100), '%' ); |
3156
|
|
|
} |
3157
|
|
|
|
3158
|
|
View Code Duplication |
public function hsvvalue( $color = null ){ |
3159
|
|
|
if (!$color instanceof Less_Tree_Color) { |
3160
|
|
|
throw new Less_Exception_Compiler('The first argument to hsvvalue must be a color' . ($color instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '') ); |
3161
|
|
|
} |
3162
|
|
|
|
3163
|
|
|
$hsv = $color->toHSV(); |
3164
|
|
|
return new Less_Tree_Dimension( Less_Parser::round($hsv['v'] * 100), '%' ); |
3165
|
|
|
} |
3166
|
|
|
|
3167
|
|
View Code Duplication |
public function red($color = null) { |
3168
|
|
|
if (!$color instanceof Less_Tree_Color) { |
3169
|
|
|
throw new Less_Exception_Compiler('The first argument to red must be a color' . ($color instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '') ); |
3170
|
|
|
} |
3171
|
|
|
|
3172
|
|
|
return new Less_Tree_Dimension( $color->rgb[0] ); |
3173
|
|
|
} |
3174
|
|
|
|
3175
|
|
View Code Duplication |
public function green($color = null) { |
3176
|
|
|
if (!$color instanceof Less_Tree_Color) { |
3177
|
|
|
throw new Less_Exception_Compiler('The first argument to green must be a color' . ($color instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '') ); |
3178
|
|
|
} |
3179
|
|
|
|
3180
|
|
|
return new Less_Tree_Dimension( $color->rgb[1] ); |
3181
|
|
|
} |
3182
|
|
|
|
3183
|
|
View Code Duplication |
public function blue($color = null) { |
3184
|
|
|
if (!$color instanceof Less_Tree_Color) { |
3185
|
|
|
throw new Less_Exception_Compiler('The first argument to blue must be a color' . ($color instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '') ); |
3186
|
|
|
} |
3187
|
|
|
|
3188
|
|
|
return new Less_Tree_Dimension( $color->rgb[2] ); |
3189
|
|
|
} |
3190
|
|
|
|
3191
|
|
View Code Duplication |
public function alpha($color = null){ |
3192
|
|
|
if (!$color instanceof Less_Tree_Color) { |
3193
|
|
|
throw new Less_Exception_Compiler('The first argument to alpha must be a color' . ($color instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '') ); |
3194
|
|
|
} |
3195
|
|
|
|
3196
|
|
|
$c = $color->toHSL(); |
3197
|
|
|
return new Less_Tree_Dimension($c['a']); |
3198
|
|
|
} |
3199
|
|
|
|
3200
|
|
View Code Duplication |
public function luma ($color = null) { |
3201
|
|
|
if (!$color instanceof Less_Tree_Color) { |
3202
|
|
|
throw new Less_Exception_Compiler('The first argument to luma must be a color' . ($color instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '') ); |
3203
|
|
|
} |
3204
|
|
|
|
3205
|
|
|
return new Less_Tree_Dimension(Less_Parser::round( $color->luma() * $color->alpha * 100), '%'); |
3206
|
|
|
} |
3207
|
|
|
|
3208
|
|
|
public function luminance( $color = null ){ |
3209
|
|
|
if (!$color instanceof Less_Tree_Color) { |
3210
|
|
|
throw new Less_Exception_Compiler('The first argument to luminance must be a color' . ($color instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '') ); |
3211
|
|
|
} |
3212
|
|
|
|
3213
|
|
|
$luminance = |
3214
|
|
|
(0.2126 * $color->rgb[0] / 255) |
3215
|
|
|
+ (0.7152 * $color->rgb[1] / 255) |
3216
|
|
|
+ (0.0722 * $color->rgb[2] / 255); |
3217
|
|
|
|
3218
|
|
|
return new Less_Tree_Dimension(Less_Parser::round( $luminance * $color->alpha * 100), '%'); |
3219
|
|
|
} |
3220
|
|
|
|
3221
|
|
|
public function saturate($color = null, $amount = null){ |
3222
|
|
|
// filter: saturate(3.2); |
3223
|
|
|
// should be kept as is, so check for color |
3224
|
|
|
if ($color instanceof Less_Tree_Dimension) { |
3225
|
|
|
return null; |
3226
|
|
|
} |
3227
|
|
|
|
3228
|
|
|
if (!$color instanceof Less_Tree_Color) { |
3229
|
|
|
throw new Less_Exception_Compiler('The first argument to saturate must be a color' . ($color instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '') ); |
3230
|
|
|
} |
3231
|
|
|
if (!$amount instanceof Less_Tree_Dimension) { |
3232
|
|
|
throw new Less_Exception_Compiler('The second argument to saturate must be a percentage' . ($amount instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '') ); |
3233
|
|
|
} |
3234
|
|
|
|
3235
|
|
|
$hsl = $color->toHSL(); |
3236
|
|
|
|
3237
|
|
|
$hsl['s'] += $amount->value / 100; |
3238
|
|
|
$hsl['s'] = self::clamp($hsl['s']); |
3239
|
|
|
|
3240
|
|
|
return $this->hsla($hsl['h'], $hsl['s'], $hsl['l'], $hsl['a']); |
3241
|
|
|
} |
3242
|
|
|
|
3243
|
|
|
/** |
3244
|
|
|
* @param Less_Tree_Dimension $amount |
3245
|
|
|
*/ |
3246
|
|
View Code Duplication |
public function desaturate($color = null, $amount = null){ |
3247
|
|
|
if (!$color instanceof Less_Tree_Color) { |
3248
|
|
|
throw new Less_Exception_Compiler('The first argument to desaturate must be a color' . ($color instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '') ); |
3249
|
|
|
} |
3250
|
|
|
if (!$amount instanceof Less_Tree_Dimension) { |
3251
|
|
|
throw new Less_Exception_Compiler('The second argument to desaturate must be a percentage' . ($amount instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '') ); |
3252
|
|
|
} |
3253
|
|
|
|
3254
|
|
|
$hsl = $color->toHSL(); |
3255
|
|
|
|
3256
|
|
|
$hsl['s'] -= $amount->value / 100; |
3257
|
|
|
$hsl['s'] = self::clamp($hsl['s']); |
3258
|
|
|
|
3259
|
|
|
return $this->hsla($hsl['h'], $hsl['s'], $hsl['l'], $hsl['a']); |
3260
|
|
|
} |
3261
|
|
|
|
3262
|
|
|
|
3263
|
|
|
|
3264
|
|
View Code Duplication |
public function lighten($color = null, $amount=null){ |
3265
|
|
|
if (!$color instanceof Less_Tree_Color) { |
3266
|
|
|
throw new Less_Exception_Compiler('The first argument to lighten must be a color' . ($color instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '') ); |
3267
|
|
|
} |
3268
|
|
|
if (!$amount instanceof Less_Tree_Dimension) { |
3269
|
|
|
throw new Less_Exception_Compiler('The second argument to lighten must be a percentage' . ($amount instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '') ); |
3270
|
|
|
} |
3271
|
|
|
|
3272
|
|
|
$hsl = $color->toHSL(); |
3273
|
|
|
|
3274
|
|
|
$hsl['l'] += $amount->value / 100; |
3275
|
|
|
$hsl['l'] = self::clamp($hsl['l']); |
3276
|
|
|
|
3277
|
|
|
return $this->hsla($hsl['h'], $hsl['s'], $hsl['l'], $hsl['a']); |
3278
|
|
|
} |
3279
|
|
|
|
3280
|
|
View Code Duplication |
public function darken($color = null, $amount = null){ |
3281
|
|
|
if (!$color instanceof Less_Tree_Color) { |
3282
|
|
|
throw new Less_Exception_Compiler('The first argument to darken must be a color' . ($color instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '') ); |
3283
|
|
|
} |
3284
|
|
|
if (!$amount instanceof Less_Tree_Dimension) { |
3285
|
|
|
throw new Less_Exception_Compiler('The second argument to darken must be a percentage' . ($amount instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '') ); |
3286
|
|
|
} |
3287
|
|
|
|
3288
|
|
|
$hsl = $color->toHSL(); |
3289
|
|
|
$hsl['l'] -= $amount->value / 100; |
3290
|
|
|
$hsl['l'] = self::clamp($hsl['l']); |
3291
|
|
|
|
3292
|
|
|
return $this->hsla($hsl['h'], $hsl['s'], $hsl['l'], $hsl['a']); |
3293
|
|
|
} |
3294
|
|
|
|
3295
|
|
View Code Duplication |
public function fadein($color = null, $amount = null){ |
3296
|
|
|
if (!$color instanceof Less_Tree_Color) { |
3297
|
|
|
throw new Less_Exception_Compiler('The first argument to fadein must be a color' . ($color instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '') ); |
3298
|
|
|
} |
3299
|
|
|
if (!$amount instanceof Less_Tree_Dimension) { |
3300
|
|
|
throw new Less_Exception_Compiler('The second argument to fadein must be a percentage' . ($amount instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '') ); |
3301
|
|
|
} |
3302
|
|
|
|
3303
|
|
|
$hsl = $color->toHSL(); |
3304
|
|
|
$hsl['a'] += $amount->value / 100; |
3305
|
|
|
$hsl['a'] = self::clamp($hsl['a']); |
3306
|
|
|
return $this->hsla($hsl['h'], $hsl['s'], $hsl['l'], $hsl['a']); |
3307
|
|
|
} |
3308
|
|
|
|
3309
|
|
View Code Duplication |
public function fadeout($color = null, $amount = null){ |
3310
|
|
|
if (!$color instanceof Less_Tree_Color) { |
3311
|
|
|
throw new Less_Exception_Compiler('The first argument to fadeout must be a color' . ($color instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '') ); |
3312
|
|
|
} |
3313
|
|
|
if (!$amount instanceof Less_Tree_Dimension) { |
3314
|
|
|
throw new Less_Exception_Compiler('The second argument to fadeout must be a percentage' . ($amount instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '') ); |
3315
|
|
|
} |
3316
|
|
|
|
3317
|
|
|
$hsl = $color->toHSL(); |
3318
|
|
|
$hsl['a'] -= $amount->value / 100; |
3319
|
|
|
$hsl['a'] = self::clamp($hsl['a']); |
3320
|
|
|
return $this->hsla($hsl['h'], $hsl['s'], $hsl['l'], $hsl['a']); |
3321
|
|
|
} |
3322
|
|
|
|
3323
|
|
View Code Duplication |
public function fade($color = null, $amount = null){ |
3324
|
|
|
if (!$color instanceof Less_Tree_Color) { |
3325
|
|
|
throw new Less_Exception_Compiler('The first argument to fade must be a color' . ($color instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '') ); |
3326
|
|
|
} |
3327
|
|
|
if (!$amount instanceof Less_Tree_Dimension) { |
3328
|
|
|
throw new Less_Exception_Compiler('The second argument to fade must be a percentage' . ($amount instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '') ); |
3329
|
|
|
} |
3330
|
|
|
|
3331
|
|
|
$hsl = $color->toHSL(); |
3332
|
|
|
|
3333
|
|
|
$hsl['a'] = $amount->value / 100; |
3334
|
|
|
$hsl['a'] = self::clamp($hsl['a']); |
3335
|
|
|
return $this->hsla($hsl['h'], $hsl['s'], $hsl['l'], $hsl['a']); |
3336
|
|
|
} |
3337
|
|
|
|
3338
|
|
|
|
3339
|
|
|
|
3340
|
|
|
public function spin($color = null, $amount = null){ |
3341
|
|
|
if (!$color instanceof Less_Tree_Color) { |
3342
|
|
|
throw new Less_Exception_Compiler('The first argument to spin must be a color' . ($color instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '') ); |
3343
|
|
|
} |
3344
|
|
|
if (!$amount instanceof Less_Tree_Dimension) { |
3345
|
|
|
throw new Less_Exception_Compiler('The second argument to spin must be a number' . ($amount instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '') ); |
3346
|
|
|
} |
3347
|
|
|
|
3348
|
|
|
$hsl = $color->toHSL(); |
3349
|
|
|
$hue = fmod($hsl['h'] + $amount->value, 360); |
3350
|
|
|
|
3351
|
|
|
$hsl['h'] = $hue < 0 ? 360 + $hue : $hue; |
3352
|
|
|
|
3353
|
|
|
return $this->hsla($hsl['h'], $hsl['s'], $hsl['l'], $hsl['a']); |
3354
|
|
|
} |
3355
|
|
|
|
3356
|
|
|
// |
3357
|
|
|
// Copyright (c) 2006-2009 Hampton Catlin, Nathan Weizenbaum, and Chris Eppstein |
3358
|
|
|
// http://sass-lang.com |
3359
|
|
|
// |
3360
|
|
|
|
3361
|
|
|
/** |
3362
|
|
|
* @param Less_Tree_Color $color1 |
3363
|
|
|
*/ |
3364
|
|
|
public function mix($color1 = null, $color2 = null, $weight = null){ |
3365
|
|
|
if (!$color1 instanceof Less_Tree_Color) { |
3366
|
|
|
throw new Less_Exception_Compiler('The first argument to mix must be a color' . ($color1 instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '') ); |
3367
|
|
|
} |
3368
|
|
|
if (!$color2 instanceof Less_Tree_Color) { |
3369
|
|
|
throw new Less_Exception_Compiler('The second argument to mix must be a color' . ($color2 instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '') ); |
3370
|
|
|
} |
3371
|
|
|
if (!$weight) { |
3372
|
|
|
$weight = new Less_Tree_Dimension('50', '%'); |
3373
|
|
|
} |
3374
|
|
|
if (!$weight instanceof Less_Tree_Dimension) { |
3375
|
|
|
throw new Less_Exception_Compiler('The third argument to contrast must be a percentage' . ($weight instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '') ); |
3376
|
|
|
} |
3377
|
|
|
|
3378
|
|
|
$p = $weight->value / 100.0; |
3379
|
|
|
$w = $p * 2 - 1; |
3380
|
|
|
$hsl1 = $color1->toHSL(); |
3381
|
|
|
$hsl2 = $color2->toHSL(); |
3382
|
|
|
$a = $hsl1['a'] - $hsl2['a']; |
3383
|
|
|
|
3384
|
|
|
$w1 = (((($w * $a) == -1) ? $w : ($w + $a) / (1 + $w * $a)) + 1) / 2; |
3385
|
|
|
$w2 = 1 - $w1; |
3386
|
|
|
|
3387
|
|
|
$rgb = array($color1->rgb[0] * $w1 + $color2->rgb[0] * $w2, |
3388
|
|
|
$color1->rgb[1] * $w1 + $color2->rgb[1] * $w2, |
3389
|
|
|
$color1->rgb[2] * $w1 + $color2->rgb[2] * $w2); |
3390
|
|
|
|
3391
|
|
|
$alpha = $color1->alpha * $p + $color2->alpha * (1 - $p); |
3392
|
|
|
|
3393
|
|
|
return new Less_Tree_Color($rgb, $alpha); |
3394
|
|
|
} |
3395
|
|
|
|
3396
|
|
|
public function greyscale($color){ |
3397
|
|
|
return $this->desaturate($color, new Less_Tree_Dimension(100,'%')); |
3398
|
|
|
} |
3399
|
|
|
|
3400
|
|
|
|
3401
|
|
|
public function contrast( $color, $dark = null, $light = null, $threshold = null){ |
3402
|
|
|
// filter: contrast(3.2); |
3403
|
|
|
// should be kept as is, so check for color |
3404
|
|
|
if (!$color instanceof Less_Tree_Color) { |
3405
|
|
|
return null; |
3406
|
|
|
} |
3407
|
|
|
if( !$light ){ |
3408
|
|
|
$light = $this->rgba(255, 255, 255, 1.0); |
3409
|
|
|
} |
3410
|
|
|
if( !$dark ){ |
3411
|
|
|
$dark = $this->rgba(0, 0, 0, 1.0); |
3412
|
|
|
} |
3413
|
|
|
|
3414
|
|
View Code Duplication |
if (!$dark instanceof Less_Tree_Color) { |
3415
|
|
|
throw new Less_Exception_Compiler('The second argument to contrast must be a color' . ($dark instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '') ); |
3416
|
|
|
} |
3417
|
|
View Code Duplication |
if (!$light instanceof Less_Tree_Color) { |
3418
|
|
|
throw new Less_Exception_Compiler('The third argument to contrast must be a color' . ($light instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '') ); |
3419
|
|
|
} |
3420
|
|
|
|
3421
|
|
|
//Figure out which is actually light and dark! |
3422
|
|
|
if( $dark->luma() > $light->luma() ){ |
3423
|
|
|
$t = $light; |
3424
|
|
|
$light = $dark; |
3425
|
|
|
$dark = $t; |
3426
|
|
|
} |
3427
|
|
|
if( !$threshold ){ |
3428
|
|
|
$threshold = 0.43; |
3429
|
|
|
} else { |
3430
|
|
|
$threshold = Less_Functions::number($threshold); |
3431
|
|
|
} |
3432
|
|
|
|
3433
|
|
|
if( $color->luma() < $threshold ){ |
3434
|
|
|
return $light; |
3435
|
|
|
} else { |
3436
|
|
|
return $dark; |
3437
|
|
|
} |
3438
|
|
|
} |
3439
|
|
|
|
3440
|
|
|
public function e ($str){ |
3441
|
|
|
if( is_string($str) ){ |
3442
|
|
|
return new Less_Tree_Anonymous($str); |
3443
|
|
|
} |
3444
|
|
|
return new Less_Tree_Anonymous($str instanceof Less_Tree_JavaScript ? $str->expression : $str->value); |
3445
|
|
|
} |
3446
|
|
|
|
3447
|
|
|
public function escape ($str){ |
3448
|
|
|
|
3449
|
|
|
$revert = array('%21'=>'!', '%2A'=>'*', '%27'=>"'",'%3F'=>'?','%26'=>'&','%2C'=>',','%2F'=>'/','%40'=>'@','%2B'=>'+','%24'=>'$'); |
3450
|
|
|
|
3451
|
|
|
return new Less_Tree_Anonymous(strtr(rawurlencode($str->value), $revert)); |
3452
|
|
|
} |
3453
|
|
|
|
3454
|
|
|
|
3455
|
|
|
/** |
3456
|
|
|
* todo: This function will need some additional work to make it work the same as less.js |
3457
|
|
|
* |
3458
|
|
|
*/ |
3459
|
|
|
public function replace( $string, $pattern, $replacement, $flags = null ){ |
3460
|
|
|
$result = $string->value; |
3461
|
|
|
|
3462
|
|
|
$expr = '/'.str_replace('/','\\/',$pattern->value).'/'; |
3463
|
|
|
if( $flags && $flags->value){ |
3464
|
|
|
$expr .= self::replace_flags($flags->value); |
3465
|
|
|
} |
3466
|
|
|
|
3467
|
|
|
$result = preg_replace($expr,$replacement->value,$result); |
3468
|
|
|
|
3469
|
|
|
|
3470
|
|
|
if( property_exists($string,'quote') ){ |
3471
|
|
|
return new Less_Tree_Quoted( $string->quote, $result, $string->escaped); |
3472
|
|
|
} |
3473
|
|
|
return new Less_Tree_Quoted( '', $result ); |
3474
|
|
|
} |
3475
|
|
|
|
3476
|
|
|
public static function replace_flags($flags){ |
3477
|
|
|
$flags = str_split($flags,1); |
3478
|
|
|
$new_flags = ''; |
3479
|
|
|
|
3480
|
|
|
foreach($flags as $flag){ |
3481
|
|
|
switch($flag){ |
3482
|
|
|
case 'e': |
3483
|
|
|
case 'g': |
3484
|
|
|
break; |
3485
|
|
|
|
3486
|
|
|
default: |
3487
|
|
|
$new_flags .= $flag; |
3488
|
|
|
break; |
3489
|
|
|
} |
3490
|
|
|
} |
3491
|
|
|
|
3492
|
|
|
return $new_flags; |
3493
|
|
|
} |
3494
|
|
|
|
3495
|
|
|
public function _percent(){ |
3496
|
|
|
$string = func_get_arg(0); |
3497
|
|
|
|
3498
|
|
|
$args = func_get_args(); |
3499
|
|
|
array_shift($args); |
3500
|
|
|
$result = $string->value; |
3501
|
|
|
|
3502
|
|
|
foreach($args as $arg){ |
3503
|
|
|
if( preg_match('/%[sda]/i',$result, $token) ){ |
3504
|
|
|
$token = $token[0]; |
3505
|
|
|
$value = stristr($token, 's') ? $arg->value : $arg->toCSS(); |
3506
|
|
|
$value = preg_match('/[A-Z]$/', $token) ? urlencode($value) : $value; |
3507
|
|
|
$result = preg_replace('/%[sda]/i',$value, $result, 1); |
3508
|
|
|
} |
3509
|
|
|
} |
3510
|
|
|
$result = str_replace('%%', '%', $result); |
3511
|
|
|
|
3512
|
|
|
return new Less_Tree_Quoted( $string->quote , $result, $string->escaped); |
3513
|
|
|
} |
3514
|
|
|
|
3515
|
|
|
public function unit( $val, $unit = null) { |
3516
|
|
|
if( !($val instanceof Less_Tree_Dimension) ){ |
3517
|
|
|
throw new Less_Exception_Compiler('The first argument to unit must be a number' . ($val instanceof Less_Tree_Operation ? '. Have you forgotten parenthesis?' : '.') ); |
3518
|
|
|
} |
3519
|
|
|
|
3520
|
|
|
if( $unit ){ |
3521
|
|
|
if( $unit instanceof Less_Tree_Keyword ){ |
3522
|
|
|
$unit = $unit->value; |
3523
|
|
|
} else { |
3524
|
|
|
$unit = $unit->toCSS(); |
3525
|
|
|
} |
3526
|
|
|
} else { |
3527
|
|
|
$unit = ""; |
3528
|
|
|
} |
3529
|
|
|
return new Less_Tree_Dimension($val->value, $unit ); |
3530
|
|
|
} |
3531
|
|
|
|
3532
|
|
|
public function convert($val, $unit){ |
3533
|
|
|
return $val->convertTo($unit->value); |
3534
|
|
|
} |
3535
|
|
|
|
3536
|
|
|
public function round($n, $f = false) { |
3537
|
|
|
|
3538
|
|
|
$fraction = 0; |
3539
|
|
|
if( $f !== false ){ |
3540
|
|
|
$fraction = $f->value; |
3541
|
|
|
} |
3542
|
|
|
|
3543
|
|
|
return $this->_math('Less_Parser::round',null, $n, $fraction); |
3544
|
|
|
} |
3545
|
|
|
|
3546
|
|
|
public function pi(){ |
3547
|
|
|
return new Less_Tree_Dimension(M_PI); |
3548
|
|
|
} |
3549
|
|
|
|
3550
|
|
|
public function mod($a, $b) { |
3551
|
|
|
return new Less_Tree_Dimension( $a->value % $b->value, $a->unit); |
3552
|
|
|
} |
3553
|
|
|
|
3554
|
|
|
|
3555
|
|
|
|
3556
|
|
|
public function pow($x, $y) { |
3557
|
|
|
if( is_numeric($x) && is_numeric($y) ){ |
3558
|
|
|
$x = new Less_Tree_Dimension($x); |
3559
|
|
|
$y = new Less_Tree_Dimension($y); |
3560
|
|
|
}elseif( !($x instanceof Less_Tree_Dimension) || !($y instanceof Less_Tree_Dimension) ){ |
3561
|
|
|
throw new Less_Exception_Compiler('Arguments must be numbers'); |
3562
|
|
|
} |
3563
|
|
|
|
3564
|
|
|
return new Less_Tree_Dimension( pow($x->value, $y->value), $x->unit ); |
3565
|
|
|
} |
3566
|
|
|
|
3567
|
|
|
// var mathFunctions = [{name:"ce ... |
3568
|
|
|
public function ceil( $n ){ return $this->_math('ceil', null, $n); } |
3569
|
|
|
public function floor( $n ){ return $this->_math('floor', null, $n); } |
3570
|
|
|
public function sqrt( $n ){ return $this->_math('sqrt', null, $n); } |
3571
|
|
|
public function abs( $n ){ return $this->_math('abs', null, $n); } |
3572
|
|
|
|
3573
|
|
|
public function tan( $n ){ return $this->_math('tan', '', $n); } |
3574
|
|
|
public function sin( $n ){ return $this->_math('sin', '', $n); } |
3575
|
|
|
public function cos( $n ){ return $this->_math('cos', '', $n); } |
3576
|
|
|
|
3577
|
|
|
public function atan( $n ){ return $this->_math('atan', 'rad', $n); } |
3578
|
|
|
public function asin( $n ){ return $this->_math('asin', 'rad', $n); } |
3579
|
|
|
public function acos( $n ){ return $this->_math('acos', 'rad', $n); } |
3580
|
|
|
|
3581
|
|
|
private function _math() { |
3582
|
|
|
$args = func_get_args(); |
3583
|
|
|
$fn = array_shift($args); |
3584
|
|
|
$unit = array_shift($args); |
3585
|
|
|
|
3586
|
|
|
if ($args[0] instanceof Less_Tree_Dimension) { |
3587
|
|
|
|
3588
|
|
|
if( $unit === null ){ |
3589
|
|
|
$unit = $args[0]->unit; |
3590
|
|
|
}else{ |
3591
|
|
|
$args[0] = $args[0]->unify(); |
3592
|
|
|
} |
3593
|
|
|
$args[0] = (float)$args[0]->value; |
3594
|
|
|
return new Less_Tree_Dimension( call_user_func_array($fn, $args), $unit); |
3595
|
|
|
} else if (is_numeric($args[0])) { |
3596
|
|
|
return call_user_func_array($fn,$args); |
3597
|
|
|
} else { |
3598
|
|
|
throw new Less_Exception_Compiler("math functions take numbers as parameters"); |
3599
|
|
|
} |
3600
|
|
|
} |
3601
|
|
|
|
3602
|
|
|
/** |
3603
|
|
|
* @param boolean $isMin |
3604
|
|
|
*/ |
3605
|
|
|
private function _minmax( $isMin, $args ){ |
3606
|
|
|
|
3607
|
|
|
$arg_count = count($args); |
3608
|
|
|
|
3609
|
|
|
if( $arg_count < 1 ){ |
3610
|
|
|
throw new Less_Exception_Compiler( 'one or more arguments required'); |
3611
|
|
|
} |
3612
|
|
|
|
3613
|
|
|
$j = null; |
3614
|
|
|
$unitClone = null; |
3615
|
|
|
$unitStatic = null; |
3616
|
|
|
|
3617
|
|
|
|
3618
|
|
|
$order = array(); // elems only contains original argument values. |
3619
|
|
|
$values = array(); // key is the unit.toString() for unified tree.Dimension values, |
3620
|
|
|
// value is the index into the order array. |
3621
|
|
|
|
3622
|
|
|
|
3623
|
|
|
for( $i = 0; $i < $arg_count; $i++ ){ |
3624
|
|
|
$current = $args[$i]; |
3625
|
|
|
if( !($current instanceof Less_Tree_Dimension) ){ |
3626
|
|
|
if( is_array($args[$i]->value) ){ |
3627
|
|
|
$args[] = $args[$i]->value; |
3628
|
|
|
} |
3629
|
|
|
continue; |
3630
|
|
|
} |
3631
|
|
|
|
3632
|
|
|
if( $current->unit->toString() === '' && !$unitClone ){ |
|
|
|
|
3633
|
|
|
$temp = new Less_Tree_Dimension($current->value, $unitClone); |
3634
|
|
|
$currentUnified = $temp->unify(); |
3635
|
|
|
}else{ |
3636
|
|
|
$currentUnified = $current->unify(); |
3637
|
|
|
} |
3638
|
|
|
|
3639
|
|
|
if( $currentUnified->unit->toString() === "" && !$unitStatic ){ |
|
|
|
|
3640
|
|
|
$unit = $unitStatic; |
3641
|
|
|
}else{ |
3642
|
|
|
$unit = $currentUnified->unit->toString(); |
3643
|
|
|
} |
3644
|
|
|
|
3645
|
|
|
if( $unit !== '' && !$unitStatic || $unit !== '' && $order[0]->unify()->unit->toString() === "" ){ |
|
|
|
|
3646
|
|
|
$unitStatic = $unit; |
3647
|
|
|
} |
3648
|
|
|
|
3649
|
|
|
if( $unit != '' && !$unitClone ){ |
|
|
|
|
3650
|
|
|
$unitClone = $current->unit->toString(); |
3651
|
|
|
} |
3652
|
|
|
|
3653
|
|
|
if( isset($values['']) && $unit !== '' && $unit === $unitStatic ){ |
3654
|
|
|
$j = $values['']; |
3655
|
|
|
}elseif( isset($values[$unit]) ){ |
3656
|
|
|
$j = $values[$unit]; |
3657
|
|
|
}else{ |
3658
|
|
|
|
3659
|
|
|
if( $unitStatic && $unit !== $unitStatic ){ |
|
|
|
|
3660
|
|
|
throw new Less_Exception_Compiler( 'incompatible types'); |
3661
|
|
|
} |
3662
|
|
|
$values[$unit] = count($order); |
3663
|
|
|
$order[] = $current; |
3664
|
|
|
continue; |
3665
|
|
|
} |
3666
|
|
|
|
3667
|
|
|
|
3668
|
|
|
if( $order[$j]->unit->toString() === "" && $unitClone ){ |
|
|
|
|
3669
|
|
|
$temp = new Less_Tree_Dimension( $order[$j]->value, $unitClone); |
3670
|
|
|
$referenceUnified = $temp->unify(); |
3671
|
|
|
}else{ |
3672
|
|
|
$referenceUnified = $order[$j]->unify(); |
3673
|
|
|
} |
3674
|
|
|
if( ($isMin && $currentUnified->value < $referenceUnified->value) || (!$isMin && $currentUnified->value > $referenceUnified->value) ){ |
3675
|
|
|
$order[$j] = $current; |
3676
|
|
|
} |
3677
|
|
|
} |
3678
|
|
|
|
3679
|
|
|
if( count($order) == 1 ){ |
3680
|
|
|
return $order[0]; |
3681
|
|
|
} |
3682
|
|
|
$args = array(); |
3683
|
|
|
foreach($order as $a){ |
3684
|
|
|
$args[] = $a->toCSS($this->env); |
3685
|
|
|
} |
3686
|
|
|
return new Less_Tree_Anonymous( ($isMin?'min(':'max(') . implode(Less_Environment::$_outputMap[','],$args).')'); |
3687
|
|
|
} |
3688
|
|
|
|
3689
|
|
|
public function min(){ |
3690
|
|
|
$args = func_get_args(); |
3691
|
|
|
return $this->_minmax( true, $args ); |
3692
|
|
|
} |
3693
|
|
|
|
3694
|
|
|
public function max(){ |
3695
|
|
|
$args = func_get_args(); |
3696
|
|
|
return $this->_minmax( false, $args ); |
3697
|
|
|
} |
3698
|
|
|
|
3699
|
|
|
public function getunit($n){ |
3700
|
|
|
return new Less_Tree_Anonymous($n->unit); |
3701
|
|
|
} |
3702
|
|
|
|
3703
|
|
View Code Duplication |
public function argb($color) { |
3704
|
|
|
if (!$color instanceof Less_Tree_Color) { |
3705
|
|
|
throw new Less_Exception_Compiler('The first argument to argb must be a color' . ($dark instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '') ); |
3706
|
|
|
} |
3707
|
|
|
|
3708
|
|
|
return new Less_Tree_Anonymous($color->toARGB()); |
3709
|
|
|
} |
3710
|
|
|
|
3711
|
|
|
public function percentage($n) { |
3712
|
|
|
return new Less_Tree_Dimension($n->value * 100, '%'); |
3713
|
|
|
} |
3714
|
|
|
|
3715
|
|
|
public function color($n) { |
3716
|
|
|
|
3717
|
|
|
if( $n instanceof Less_Tree_Quoted ){ |
3718
|
|
|
$colorCandidate = $n->value; |
3719
|
|
|
$returnColor = Less_Tree_Color::fromKeyword($colorCandidate); |
3720
|
|
|
if( $returnColor ){ |
3721
|
|
|
return $returnColor; |
3722
|
|
|
} |
3723
|
|
|
if( preg_match('/^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})/',$colorCandidate) ){ |
3724
|
|
|
return new Less_Tree_Color(substr($colorCandidate, 1)); |
3725
|
|
|
} |
3726
|
|
|
throw new Less_Exception_Compiler("argument must be a color keyword or 3/6 digit hex e.g. #FFF"); |
3727
|
|
|
} else { |
3728
|
|
|
throw new Less_Exception_Compiler("argument must be a string"); |
3729
|
|
|
} |
3730
|
|
|
} |
3731
|
|
|
|
3732
|
|
|
|
3733
|
|
|
public function iscolor($n) { |
3734
|
|
|
return $this->_isa($n, 'Less_Tree_Color'); |
3735
|
|
|
} |
3736
|
|
|
|
3737
|
|
|
public function isnumber($n) { |
3738
|
|
|
return $this->_isa($n, 'Less_Tree_Dimension'); |
3739
|
|
|
} |
3740
|
|
|
|
3741
|
|
|
public function isstring($n) { |
3742
|
|
|
return $this->_isa($n, 'Less_Tree_Quoted'); |
3743
|
|
|
} |
3744
|
|
|
|
3745
|
|
|
public function iskeyword($n) { |
3746
|
|
|
return $this->_isa($n, 'Less_Tree_Keyword'); |
3747
|
|
|
} |
3748
|
|
|
|
3749
|
|
|
public function isurl($n) { |
3750
|
|
|
return $this->_isa($n, 'Less_Tree_Url'); |
3751
|
|
|
} |
3752
|
|
|
|
3753
|
|
|
public function ispixel($n) { |
3754
|
|
|
return $this->isunit($n, 'px'); |
3755
|
|
|
} |
3756
|
|
|
|
3757
|
|
|
public function ispercentage($n) { |
3758
|
|
|
return $this->isunit($n, '%'); |
3759
|
|
|
} |
3760
|
|
|
|
3761
|
|
|
public function isem($n) { |
3762
|
|
|
return $this->isunit($n, 'em'); |
3763
|
|
|
} |
3764
|
|
|
|
3765
|
|
|
/** |
3766
|
|
|
* @param string $unit |
3767
|
|
|
*/ |
3768
|
|
|
public function isunit( $n, $unit ){ |
3769
|
|
|
return ($n instanceof Less_Tree_Dimension) && $n->unit->is( ( property_exists($unit,'value') ? $unit->value : $unit) ) ? new Less_Tree_Keyword('true') : new Less_Tree_Keyword('false'); |
3770
|
|
|
} |
3771
|
|
|
|
3772
|
|
|
/** |
3773
|
|
|
* @param string $type |
3774
|
|
|
*/ |
3775
|
|
|
private function _isa($n, $type) { |
3776
|
|
|
return is_a($n, $type) ? new Less_Tree_Keyword('true') : new Less_Tree_Keyword('false'); |
3777
|
|
|
} |
3778
|
|
|
|
3779
|
|
|
public function tint($color, $amount) { |
3780
|
|
|
return $this->mix( $this->rgb(255,255,255), $color, $amount); |
3781
|
|
|
} |
3782
|
|
|
|
3783
|
|
|
public function shade($color, $amount) { |
3784
|
|
|
return $this->mix($this->rgb(0, 0, 0), $color, $amount); |
3785
|
|
|
} |
3786
|
|
|
|
3787
|
|
|
public function extract($values, $index ){ |
3788
|
|
|
$index = (int)$index->value - 1; // (1-based index) |
3789
|
|
|
// handle non-array values as an array of length 1 |
3790
|
|
|
// return 'undefined' if index is invalid |
3791
|
|
|
if( property_exists($values,'value') && is_array($values->value) ){ |
3792
|
|
|
if( isset($values->value[$index]) ){ |
3793
|
|
|
return $values->value[$index]; |
3794
|
|
|
} |
3795
|
|
|
return null; |
3796
|
|
|
|
3797
|
|
|
}elseif( (int)$index === 0 ){ |
3798
|
|
|
return $values; |
3799
|
|
|
} |
3800
|
|
|
|
3801
|
|
|
return null; |
3802
|
|
|
} |
3803
|
|
|
|
3804
|
|
|
public function length($values){ |
3805
|
|
|
$n = (property_exists($values,'value') && is_array($values->value)) ? count($values->value) : 1; |
3806
|
|
|
return new Less_Tree_Dimension($n); |
3807
|
|
|
} |
3808
|
|
|
|
3809
|
|
|
public function datauri($mimetypeNode, $filePathNode = null ) { |
3810
|
|
|
|
3811
|
|
|
$filePath = ( $filePathNode ? $filePathNode->value : null ); |
3812
|
|
|
$mimetype = $mimetypeNode->value; |
3813
|
|
|
|
3814
|
|
|
$args = 2; |
3815
|
|
|
if( !$filePath ){ |
3816
|
|
|
$filePath = $mimetype; |
3817
|
|
|
$args = 1; |
3818
|
|
|
} |
3819
|
|
|
|
3820
|
|
|
$filePath = str_replace('\\','/',$filePath); |
3821
|
|
|
if( Less_Environment::isPathRelative($filePath) ){ |
3822
|
|
|
|
3823
|
|
|
if( Less_Parser::$options['relativeUrls'] ){ |
3824
|
|
|
$temp = $this->currentFileInfo['currentDirectory']; |
3825
|
|
|
} else { |
3826
|
|
|
$temp = $this->currentFileInfo['entryPath']; |
3827
|
|
|
} |
3828
|
|
|
|
3829
|
|
|
if( !empty($temp) ){ |
3830
|
|
|
$filePath = Less_Environment::normalizePath(rtrim($temp,'/').'/'.$filePath); |
3831
|
|
|
} |
3832
|
|
|
|
3833
|
|
|
} |
3834
|
|
|
|
3835
|
|
|
|
3836
|
|
|
// detect the mimetype if not given |
3837
|
|
|
if( $args < 2 ){ |
3838
|
|
|
|
3839
|
|
|
/* incomplete |
3840
|
|
|
$mime = require('mime'); |
3841
|
|
|
mimetype = mime.lookup(path); |
3842
|
|
|
|
3843
|
|
|
// use base 64 unless it's an ASCII or UTF-8 format |
3844
|
|
|
var charset = mime.charsets.lookup(mimetype); |
3845
|
|
|
useBase64 = ['US-ASCII', 'UTF-8'].indexOf(charset) < 0; |
3846
|
|
|
if (useBase64) mimetype += ';base64'; |
3847
|
|
|
*/ |
3848
|
|
|
|
3849
|
|
|
$mimetype = Less_Mime::lookup($filePath); |
3850
|
|
|
|
3851
|
|
|
$charset = Less_Mime::charsets_lookup($mimetype); |
3852
|
|
|
$useBase64 = !in_array($charset,array('US-ASCII', 'UTF-8')); |
3853
|
|
|
if( $useBase64 ){ $mimetype .= ';base64'; } |
3854
|
|
|
|
3855
|
|
|
}else{ |
3856
|
|
|
$useBase64 = preg_match('/;base64$/',$mimetype); |
3857
|
|
|
} |
3858
|
|
|
|
3859
|
|
|
|
3860
|
|
|
if( file_exists($filePath) ){ |
3861
|
|
|
$buf = @file_get_contents($filePath); |
3862
|
|
|
}else{ |
3863
|
|
|
$buf = false; |
3864
|
|
|
} |
3865
|
|
|
|
3866
|
|
|
|
3867
|
|
|
// IE8 cannot handle a data-uri larger than 32KB. If this is exceeded |
3868
|
|
|
// and the --ieCompat flag is enabled, return a normal url() instead. |
3869
|
|
|
$DATA_URI_MAX_KB = 32; |
3870
|
|
|
$fileSizeInKB = round( strlen($buf) / 1024 ); |
3871
|
|
|
if( $fileSizeInKB >= $DATA_URI_MAX_KB ){ |
3872
|
|
|
$url = new Less_Tree_Url( ($filePathNode ? $filePathNode : $mimetypeNode), $this->currentFileInfo); |
3873
|
|
|
return $url->compile($this); |
3874
|
|
|
} |
3875
|
|
|
|
3876
|
|
|
if( $buf ){ |
3877
|
|
|
$buf = $useBase64 ? base64_encode($buf) : rawurlencode($buf); |
3878
|
|
|
$filePath = '"data:' . $mimetype . ',' . $buf . '"'; |
3879
|
|
|
} |
3880
|
|
|
|
3881
|
|
|
return new Less_Tree_Url( new Less_Tree_Anonymous($filePath) ); |
3882
|
|
|
} |
3883
|
|
|
|
3884
|
|
|
//svg-gradient |
3885
|
|
|
public function svggradient( $direction ){ |
3886
|
|
|
|
3887
|
|
|
$throw_message = 'svg-gradient expects direction, start_color [start_position], [color position,]..., end_color [end_position]'; |
3888
|
|
|
$arguments = func_get_args(); |
3889
|
|
|
|
3890
|
|
|
if( count($arguments) < 3 ){ |
3891
|
|
|
throw new Less_Exception_Compiler( $throw_message ); |
3892
|
|
|
} |
3893
|
|
|
|
3894
|
|
|
$stops = array_slice($arguments,1); |
3895
|
|
|
$gradientType = 'linear'; |
3896
|
|
|
$rectangleDimension = 'x="0" y="0" width="1" height="1"'; |
3897
|
|
|
$useBase64 = true; |
3898
|
|
|
$directionValue = $direction->toCSS(); |
3899
|
|
|
|
3900
|
|
|
|
3901
|
|
|
switch( $directionValue ){ |
3902
|
|
|
case "to bottom": |
3903
|
|
|
$gradientDirectionSvg = 'x1="0%" y1="0%" x2="0%" y2="100%"'; |
3904
|
|
|
break; |
3905
|
|
|
case "to right": |
3906
|
|
|
$gradientDirectionSvg = 'x1="0%" y1="0%" x2="100%" y2="0%"'; |
3907
|
|
|
break; |
3908
|
|
|
case "to bottom right": |
3909
|
|
|
$gradientDirectionSvg = 'x1="0%" y1="0%" x2="100%" y2="100%"'; |
3910
|
|
|
break; |
3911
|
|
|
case "to top right": |
3912
|
|
|
$gradientDirectionSvg = 'x1="0%" y1="100%" x2="100%" y2="0%"'; |
3913
|
|
|
break; |
3914
|
|
|
case "ellipse": |
3915
|
|
|
case "ellipse at center": |
3916
|
|
|
$gradientType = "radial"; |
3917
|
|
|
$gradientDirectionSvg = 'cx="50%" cy="50%" r="75%"'; |
3918
|
|
|
$rectangleDimension = 'x="-50" y="-50" width="101" height="101"'; |
3919
|
|
|
break; |
3920
|
|
|
default: |
3921
|
|
|
throw new Less_Exception_Compiler( "svg-gradient direction must be 'to bottom', 'to right', 'to bottom right', 'to top right' or 'ellipse at center'" ); |
3922
|
|
|
} |
3923
|
|
|
|
3924
|
|
|
$returner = '<?xml version="1.0" ?>' . |
3925
|
|
|
'<svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="100%" height="100%" viewBox="0 0 1 1" preserveAspectRatio="none">' . |
3926
|
|
|
'<' . $gradientType . 'Gradient id="gradient" gradientUnits="userSpaceOnUse" ' . $gradientDirectionSvg . '>'; |
3927
|
|
|
|
3928
|
|
|
for( $i = 0; $i < count($stops); $i++ ){ |
|
|
|
|
3929
|
|
|
if( is_object($stops[$i]) && property_exists($stops[$i],'value') ){ |
3930
|
|
|
$color = $stops[$i]->value[0]; |
3931
|
|
|
$position = $stops[$i]->value[1]; |
3932
|
|
|
}else{ |
3933
|
|
|
$color = $stops[$i]; |
3934
|
|
|
$position = null; |
3935
|
|
|
} |
3936
|
|
|
|
3937
|
|
|
if( !($color instanceof Less_Tree_Color) || (!(($i === 0 || $i+1 === count($stops)) && $position === null) && !($position instanceof Less_Tree_Dimension)) ){ |
3938
|
|
|
throw new Less_Exception_Compiler( $throw_message ); |
3939
|
|
|
} |
3940
|
|
|
if( $position ){ |
3941
|
|
|
$positionValue = $position->toCSS(); |
3942
|
|
|
}elseif( $i === 0 ){ |
3943
|
|
|
$positionValue = '0%'; |
3944
|
|
|
}else{ |
3945
|
|
|
$positionValue = '100%'; |
3946
|
|
|
} |
3947
|
|
|
$alpha = $color->alpha; |
3948
|
|
|
$returner .= '<stop offset="' . $positionValue . '" stop-color="' . $color->toRGB() . '"' . ($alpha < 1 ? ' stop-opacity="' . $alpha . '"' : '') . '/>'; |
3949
|
|
|
} |
3950
|
|
|
|
3951
|
|
|
$returner .= '</' . $gradientType . 'Gradient><rect ' . $rectangleDimension . ' fill="url(#gradient)" /></svg>'; |
3952
|
|
|
|
3953
|
|
|
|
3954
|
|
|
if( $useBase64 ){ |
3955
|
|
|
$returner = "'data:image/svg+xml;base64,".base64_encode($returner)."'"; |
3956
|
|
|
}else{ |
3957
|
|
|
$returner = "'data:image/svg+xml,".$returner."'"; |
3958
|
|
|
} |
3959
|
|
|
|
3960
|
|
|
return new Less_Tree_URL( new Less_Tree_Anonymous( $returner ) ); |
3961
|
|
|
} |
3962
|
|
|
|
3963
|
|
|
|
3964
|
|
|
/** |
3965
|
|
|
* Php version of javascript's `encodeURIComponent` function |
3966
|
|
|
* |
3967
|
|
|
* @param string $string The string to encode |
3968
|
|
|
* @return string The encoded string |
3969
|
|
|
*/ |
3970
|
|
|
public static function encodeURIComponent($string){ |
3971
|
|
|
$revert = array('%21' => '!', '%2A' => '*', '%27' => "'", '%28' => '(', '%29' => ')'); |
3972
|
|
|
return strtr(rawurlencode($string), $revert); |
3973
|
|
|
} |
3974
|
|
|
|
3975
|
|
|
|
3976
|
|
|
// Color Blending |
3977
|
|
|
// ref: http://www.w3.org/TR/compositing-1 |
3978
|
|
|
|
3979
|
|
|
public function colorBlend( $mode, $color1, $color2 ){ |
3980
|
|
|
$ab = $color1->alpha; // backdrop |
3981
|
|
|
$as = $color2->alpha; // source |
3982
|
|
|
$r = array(); // result |
3983
|
|
|
|
3984
|
|
|
$ar = $as + $ab * (1 - $as); |
3985
|
|
|
for( $i = 0; $i < 3; $i++ ){ |
3986
|
|
|
$cb = $color1->rgb[$i] / 255; |
3987
|
|
|
$cs = $color2->rgb[$i] / 255; |
3988
|
|
|
$cr = call_user_func( $mode, $cb, $cs ); |
3989
|
|
|
if( $ar ){ |
3990
|
|
|
$cr = ($as * $cs + $ab * ($cb - $as * ($cb + $cs - $cr))) / $ar; |
3991
|
|
|
} |
3992
|
|
|
$r[$i] = $cr * 255; |
3993
|
|
|
} |
3994
|
|
|
|
3995
|
|
|
return new Less_Tree_Color($r, $ar); |
3996
|
|
|
} |
3997
|
|
|
|
3998
|
|
View Code Duplication |
public function multiply($color1 = null, $color2 = null ){ |
3999
|
|
|
if (!$color1 instanceof Less_Tree_Color) { |
4000
|
|
|
throw new Less_Exception_Compiler('The first argument to multiply must be a color' . ($color instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '') ); |
4001
|
|
|
} |
4002
|
|
|
if (!$color2 instanceof Less_Tree_Color) { |
4003
|
|
|
throw new Less_Exception_Compiler('The second argument to multiply must be a color' . ($color instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '') ); |
4004
|
|
|
} |
4005
|
|
|
|
4006
|
|
|
return $this->colorBlend( array($this,'colorBlendMultiply'), $color1, $color2 ); |
4007
|
|
|
} |
4008
|
|
|
|
4009
|
|
|
private function colorBlendMultiply($cb, $cs){ |
4010
|
|
|
return $cb * $cs; |
4011
|
|
|
} |
4012
|
|
|
|
4013
|
|
View Code Duplication |
public function screen($color1 = null, $color2 = null ){ |
4014
|
|
|
if (!$color1 instanceof Less_Tree_Color) { |
4015
|
|
|
throw new Less_Exception_Compiler('The first argument to screen must be a color' . ($color instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '') ); |
4016
|
|
|
} |
4017
|
|
|
if (!$color2 instanceof Less_Tree_Color) { |
4018
|
|
|
throw new Less_Exception_Compiler('The second argument to screen must be a color' . ($color instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '') ); |
4019
|
|
|
} |
4020
|
|
|
|
4021
|
|
|
return $this->colorBlend( array($this,'colorBlendScreen'), $color1, $color2 ); |
4022
|
|
|
} |
4023
|
|
|
|
4024
|
|
|
private function colorBlendScreen( $cb, $cs){ |
4025
|
|
|
return $cb + $cs - $cb * $cs; |
4026
|
|
|
} |
4027
|
|
|
|
4028
|
|
View Code Duplication |
public function overlay($color1 = null, $color2 = null){ |
4029
|
|
|
if (!$color1 instanceof Less_Tree_Color) { |
4030
|
|
|
throw new Less_Exception_Compiler('The first argument to overlay must be a color' . ($color instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '') ); |
4031
|
|
|
} |
4032
|
|
|
if (!$color2 instanceof Less_Tree_Color) { |
4033
|
|
|
throw new Less_Exception_Compiler('The second argument to overlay must be a color' . ($color instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '') ); |
4034
|
|
|
} |
4035
|
|
|
|
4036
|
|
|
return $this->colorBlend( array($this,'colorBlendOverlay'), $color1, $color2 ); |
4037
|
|
|
} |
4038
|
|
|
|
4039
|
|
|
private function colorBlendOverlay($cb, $cs ){ |
4040
|
|
|
$cb *= 2; |
4041
|
|
|
return ($cb <= 1) |
4042
|
|
|
? $this->colorBlendMultiply($cb, $cs) |
4043
|
|
|
: $this->colorBlendScreen($cb - 1, $cs); |
4044
|
|
|
} |
4045
|
|
|
|
4046
|
|
View Code Duplication |
public function softlight($color1 = null, $color2 = null){ |
4047
|
|
|
if (!$color1 instanceof Less_Tree_Color) { |
4048
|
|
|
throw new Less_Exception_Compiler('The first argument to softlight must be a color' . ($color instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '') ); |
4049
|
|
|
} |
4050
|
|
|
if (!$color2 instanceof Less_Tree_Color) { |
4051
|
|
|
throw new Less_Exception_Compiler('The second argument to softlight must be a color' . ($color instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '') ); |
4052
|
|
|
} |
4053
|
|
|
|
4054
|
|
|
return $this->colorBlend( array($this,'colorBlendSoftlight'), $color1, $color2 ); |
4055
|
|
|
} |
4056
|
|
|
|
4057
|
|
|
private function colorBlendSoftlight($cb, $cs ){ |
4058
|
|
|
$d = 1; |
4059
|
|
|
$e = $cb; |
4060
|
|
|
if( $cs > 0.5 ){ |
4061
|
|
|
$e = 1; |
4062
|
|
|
$d = ($cb > 0.25) ? sqrt($cb) |
4063
|
|
|
: ((16 * $cb - 12) * $cb + 4) * $cb; |
4064
|
|
|
} |
4065
|
|
|
return $cb - (1 - 2 * $cs) * $e * ($d - $cb); |
4066
|
|
|
} |
4067
|
|
|
|
4068
|
|
View Code Duplication |
public function hardlight($color1 = null, $color2 = null){ |
4069
|
|
|
if (!$color1 instanceof Less_Tree_Color) { |
4070
|
|
|
throw new Less_Exception_Compiler('The first argument to hardlight must be a color' . ($color instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '') ); |
4071
|
|
|
} |
4072
|
|
|
if (!$color2 instanceof Less_Tree_Color) { |
4073
|
|
|
throw new Less_Exception_Compiler('The second argument to hardlight must be a color' . ($color instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '') ); |
4074
|
|
|
} |
4075
|
|
|
|
4076
|
|
|
return $this->colorBlend( array($this,'colorBlendHardlight'), $color1, $color2 ); |
4077
|
|
|
} |
4078
|
|
|
|
4079
|
|
|
private function colorBlendHardlight( $cb, $cs ){ |
4080
|
|
|
return $this->colorBlendOverlay($cs, $cb); |
4081
|
|
|
} |
4082
|
|
|
|
4083
|
|
View Code Duplication |
public function difference($color1 = null, $color2 = null) { |
4084
|
|
|
if (!$color1 instanceof Less_Tree_Color) { |
4085
|
|
|
throw new Less_Exception_Compiler('The first argument to difference must be a color' . ($color instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '') ); |
4086
|
|
|
} |
4087
|
|
|
if (!$color2 instanceof Less_Tree_Color) { |
4088
|
|
|
throw new Less_Exception_Compiler('The second argument to difference must be a color' . ($color instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '') ); |
4089
|
|
|
} |
4090
|
|
|
|
4091
|
|
|
return $this->colorBlend( array($this,'colorBlendDifference'), $color1, $color2 ); |
4092
|
|
|
} |
4093
|
|
|
|
4094
|
|
|
private function colorBlendDifference( $cb, $cs ){ |
4095
|
|
|
return abs($cb - $cs); |
4096
|
|
|
} |
4097
|
|
|
|
4098
|
|
View Code Duplication |
public function exclusion( $color1 = null, $color2 = null ){ |
4099
|
|
|
if (!$color1 instanceof Less_Tree_Color) { |
4100
|
|
|
throw new Less_Exception_Compiler('The first argument to exclusion must be a color' . ($color instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '') ); |
4101
|
|
|
} |
4102
|
|
|
if (!$color2 instanceof Less_Tree_Color) { |
4103
|
|
|
throw new Less_Exception_Compiler('The second argument to exclusion must be a color' . ($color instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '') ); |
4104
|
|
|
} |
4105
|
|
|
|
4106
|
|
|
return $this->colorBlend( array($this,'colorBlendExclusion'), $color1, $color2 ); |
4107
|
|
|
} |
4108
|
|
|
|
4109
|
|
|
private function colorBlendExclusion( $cb, $cs ){ |
4110
|
|
|
return $cb + $cs - 2 * $cb * $cs; |
4111
|
|
|
} |
4112
|
|
|
|
4113
|
|
View Code Duplication |
public function average($color1 = null, $color2 = null){ |
4114
|
|
|
if (!$color1 instanceof Less_Tree_Color) { |
4115
|
|
|
throw new Less_Exception_Compiler('The first argument to average must be a color' . ($color instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '') ); |
4116
|
|
|
} |
4117
|
|
|
if (!$color2 instanceof Less_Tree_Color) { |
4118
|
|
|
throw new Less_Exception_Compiler('The second argument to average must be a color' . ($color instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '') ); |
4119
|
|
|
} |
4120
|
|
|
|
4121
|
|
|
return $this->colorBlend( array($this,'colorBlendAverage'), $color1, $color2 ); |
4122
|
|
|
} |
4123
|
|
|
|
4124
|
|
|
// non-w3c functions: |
4125
|
|
|
public function colorBlendAverage($cb, $cs ){ |
4126
|
|
|
return ($cb + $cs) / 2; |
4127
|
|
|
} |
4128
|
|
|
|
4129
|
|
View Code Duplication |
public function negation($color1 = null, $color2 = null ){ |
4130
|
|
|
if (!$color1 instanceof Less_Tree_Color) { |
4131
|
|
|
throw new Less_Exception_Compiler('The first argument to negation must be a color' . ($color instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '') ); |
4132
|
|
|
} |
4133
|
|
|
if (!$color2 instanceof Less_Tree_Color) { |
4134
|
|
|
throw new Less_Exception_Compiler('The second argument to negation must be a color' . ($color instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '') ); |
4135
|
|
|
} |
4136
|
|
|
|
4137
|
|
|
return $this->colorBlend( array($this,'colorBlendNegation'), $color1, $color2 ); |
4138
|
|
|
} |
4139
|
|
|
|
4140
|
|
|
public function colorBlendNegation($cb, $cs){ |
4141
|
|
|
return 1 - abs($cb + $cs - 1); |
4142
|
|
|
} |
4143
|
|
|
|
4144
|
|
|
// ~ End of Color Blending |
4145
|
|
|
|
4146
|
|
|
} |
4147
|
|
|
|
4148
|
|
|
|
4149
|
|
|
/** |
4150
|
|
|
* Mime lookup |
4151
|
|
|
* |
4152
|
|
|
* @package Less |
4153
|
|
|
* @subpackage node |
4154
|
|
|
*/ |
4155
|
|
|
class Less_Mime{ |
4156
|
|
|
|
4157
|
|
|
// this map is intentionally incomplete |
4158
|
|
|
// if you want more, install 'mime' dep |
4159
|
|
|
static $_types = array( |
4160
|
|
|
'.htm' => 'text/html', |
4161
|
|
|
'.html'=> 'text/html', |
4162
|
|
|
'.gif' => 'image/gif', |
4163
|
|
|
'.jpg' => 'image/jpeg', |
4164
|
|
|
'.jpeg'=> 'image/jpeg', |
4165
|
|
|
'.png' => 'image/png', |
4166
|
|
|
'.ttf' => 'application/x-font-ttf', |
4167
|
|
|
'.otf' => 'application/x-font-otf', |
4168
|
|
|
'.eot' => 'application/vnd.ms-fontobject', |
4169
|
|
|
'.woff' => 'application/x-font-woff', |
4170
|
|
|
'.svg' => 'image/svg+xml', |
4171
|
|
|
); |
4172
|
|
|
|
4173
|
|
|
public static function lookup( $filepath ){ |
4174
|
|
|
$parts = explode('.',$filepath); |
4175
|
|
|
$ext = '.'.strtolower(array_pop($parts)); |
4176
|
|
|
|
4177
|
|
|
if( !isset(self::$_types[$ext]) ){ |
4178
|
|
|
return null; |
4179
|
|
|
} |
4180
|
|
|
return self::$_types[$ext]; |
4181
|
|
|
} |
4182
|
|
|
|
4183
|
|
|
public static function charsets_lookup( $type = null ){ |
4184
|
|
|
// assumes all text types are UTF-8 |
4185
|
|
|
return $type && preg_match('/^text\//',$type) ? 'UTF-8' : ''; |
4186
|
|
|
} |
4187
|
|
|
} |
4188
|
|
|
|
4189
|
|
|
|
4190
|
|
|
/** |
4191
|
|
|
* Tree |
4192
|
|
|
* |
4193
|
|
|
* @package Less |
4194
|
|
|
* @subpackage tree |
4195
|
|
|
*/ |
4196
|
|
|
class Less_Tree{ |
4197
|
|
|
|
4198
|
|
|
public $cache_string; |
4199
|
|
|
|
4200
|
|
|
public function toCSS(){ |
4201
|
|
|
$output = new Less_Output(); |
4202
|
|
|
$this->genCSS($output); |
4203
|
|
|
return $output->toString(); |
4204
|
|
|
} |
4205
|
|
|
|
4206
|
|
|
|
4207
|
|
|
/** |
4208
|
|
|
* Generate CSS by adding it to the output object |
4209
|
|
|
* |
4210
|
|
|
* @param Less_Output $output The output |
4211
|
|
|
* @return void |
4212
|
|
|
*/ |
4213
|
|
|
public function genCSS($output){} |
4214
|
|
|
|
4215
|
|
|
|
4216
|
|
|
/** |
4217
|
|
|
* @param Less_Tree_Ruleset[] $rules |
4218
|
|
|
*/ |
4219
|
|
|
public static function outputRuleset( $output, $rules ){ |
4220
|
|
|
|
4221
|
|
|
$ruleCnt = count($rules); |
4222
|
|
|
Less_Environment::$tabLevel++; |
4223
|
|
|
|
4224
|
|
|
|
4225
|
|
|
// Compressed |
4226
|
|
|
if( Less_Parser::$options['compress'] ){ |
4227
|
|
|
$output->add('{'); |
4228
|
|
|
for( $i = 0; $i < $ruleCnt; $i++ ){ |
4229
|
|
|
$rules[$i]->genCSS( $output ); |
4230
|
|
|
} |
4231
|
|
|
|
4232
|
|
|
$output->add( '}' ); |
4233
|
|
|
Less_Environment::$tabLevel--; |
4234
|
|
|
return; |
4235
|
|
|
} |
4236
|
|
|
|
4237
|
|
|
|
4238
|
|
|
// Non-compressed |
4239
|
|
|
$tabSetStr = "\n".str_repeat( ' ' , Less_Environment::$tabLevel-1 ); |
4240
|
|
|
$tabRuleStr = $tabSetStr.' '; |
4241
|
|
|
|
4242
|
|
|
$output->add( " {" ); |
4243
|
|
|
for($i = 0; $i < $ruleCnt; $i++ ){ |
4244
|
|
|
$output->add( $tabRuleStr ); |
4245
|
|
|
$rules[$i]->genCSS( $output ); |
4246
|
|
|
} |
4247
|
|
|
Less_Environment::$tabLevel--; |
4248
|
|
|
$output->add( $tabSetStr.'}' ); |
4249
|
|
|
|
4250
|
|
|
} |
4251
|
|
|
|
4252
|
|
|
public function accept($visitor){} |
4253
|
|
|
|
4254
|
|
|
|
4255
|
|
|
public static function ReferencedArray($rules){ |
4256
|
|
|
foreach($rules as $rule){ |
4257
|
|
|
if( method_exists($rule, 'markReferenced') ){ |
4258
|
|
|
$rule->markReferenced(); |
4259
|
|
|
} |
4260
|
|
|
} |
4261
|
|
|
} |
4262
|
|
|
|
4263
|
|
|
|
4264
|
|
|
/** |
4265
|
|
|
* Requires php 5.3+ |
4266
|
|
|
*/ |
4267
|
|
|
public static function __set_state($args){ |
4268
|
|
|
|
4269
|
|
|
$class = get_called_class(); |
4270
|
|
|
$obj = new $class(null,null,null,null); |
4271
|
|
|
foreach($args as $key => $val){ |
4272
|
|
|
$obj->$key = $val; |
4273
|
|
|
} |
4274
|
|
|
return $obj; |
4275
|
|
|
} |
4276
|
|
|
|
4277
|
|
|
} |
4278
|
|
|
|
4279
|
|
|
/** |
4280
|
|
|
* Parser output |
4281
|
|
|
* |
4282
|
|
|
* @package Less |
4283
|
|
|
* @subpackage output |
4284
|
|
|
*/ |
4285
|
|
|
class Less_Output{ |
4286
|
|
|
|
4287
|
|
|
/** |
4288
|
|
|
* Output holder |
4289
|
|
|
* |
4290
|
|
|
* @var string |
4291
|
|
|
*/ |
4292
|
|
|
protected $strs = array(); |
4293
|
|
|
|
4294
|
|
|
/** |
4295
|
|
|
* Adds a chunk to the stack |
4296
|
|
|
* |
4297
|
|
|
* @param string $chunk The chunk to output |
4298
|
|
|
* @param Less_FileInfo $fileInfo The file information |
4299
|
|
|
* @param integer $index The index |
4300
|
|
|
* @param mixed $mapLines |
4301
|
|
|
*/ |
4302
|
|
|
public function add($chunk, $fileInfo = null, $index = 0, $mapLines = null){ |
4303
|
|
|
$this->strs[] = $chunk; |
4304
|
|
|
} |
4305
|
|
|
|
4306
|
|
|
/** |
4307
|
|
|
* Is the output empty? |
4308
|
|
|
* |
4309
|
|
|
* @return boolean |
4310
|
|
|
*/ |
4311
|
|
|
public function isEmpty(){ |
4312
|
|
|
return count($this->strs) === 0; |
4313
|
|
|
} |
4314
|
|
|
|
4315
|
|
|
|
4316
|
|
|
/** |
4317
|
|
|
* Converts the output to string |
4318
|
|
|
* |
4319
|
|
|
* @return string |
4320
|
|
|
*/ |
4321
|
|
|
public function toString(){ |
4322
|
|
|
return implode('',$this->strs); |
4323
|
|
|
} |
4324
|
|
|
|
4325
|
|
|
} |
4326
|
|
|
|
4327
|
|
|
/** |
4328
|
|
|
* Visitor |
4329
|
|
|
* |
4330
|
|
|
* @package Less |
4331
|
|
|
* @subpackage visitor |
4332
|
|
|
*/ |
4333
|
|
|
class Less_Visitor{ |
4334
|
|
|
|
4335
|
|
|
protected $methods = array(); |
4336
|
|
|
protected $_visitFnCache = array(); |
4337
|
|
|
|
4338
|
|
|
public function __construct(){ |
4339
|
|
|
$this->_visitFnCache = get_class_methods(get_class($this)); |
4340
|
|
|
$this->_visitFnCache = array_flip($this->_visitFnCache); |
4341
|
|
|
} |
4342
|
|
|
|
4343
|
|
|
public function visitObj( $node ){ |
4344
|
|
|
|
4345
|
|
|
$funcName = 'visit'.$node->type; |
4346
|
|
|
if( isset($this->_visitFnCache[$funcName]) ){ |
4347
|
|
|
|
4348
|
|
|
$visitDeeper = true; |
4349
|
|
|
$this->$funcName( $node, $visitDeeper ); |
4350
|
|
|
|
4351
|
|
|
if( $visitDeeper ){ |
4352
|
|
|
$node->accept($this); |
4353
|
|
|
} |
4354
|
|
|
|
4355
|
|
|
$funcName = $funcName . "Out"; |
4356
|
|
|
if( isset($this->_visitFnCache[$funcName]) ){ |
4357
|
|
|
$this->$funcName( $node ); |
4358
|
|
|
} |
4359
|
|
|
|
4360
|
|
|
}else{ |
4361
|
|
|
$node->accept($this); |
4362
|
|
|
} |
4363
|
|
|
|
4364
|
|
|
return $node; |
4365
|
|
|
} |
4366
|
|
|
|
4367
|
|
|
public function visitArray( $nodes ){ |
4368
|
|
|
|
4369
|
|
|
array_map( array($this,'visitObj'), $nodes); |
4370
|
|
|
return $nodes; |
4371
|
|
|
} |
4372
|
|
|
} |
4373
|
|
|
|
4374
|
|
|
|
4375
|
|
|
|
4376
|
|
|
/** |
4377
|
|
|
* Replacing Visitor |
4378
|
|
|
* |
4379
|
|
|
* @package Less |
4380
|
|
|
* @subpackage visitor |
4381
|
|
|
*/ |
4382
|
|
|
class Less_VisitorReplacing extends Less_Visitor{ |
4383
|
|
|
|
4384
|
|
|
public function visitObj( $node ){ |
4385
|
|
|
|
4386
|
|
|
$funcName = 'visit'.$node->type; |
4387
|
|
|
if( isset($this->_visitFnCache[$funcName]) ){ |
4388
|
|
|
|
4389
|
|
|
$visitDeeper = true; |
4390
|
|
|
$node = $this->$funcName( $node, $visitDeeper ); |
4391
|
|
|
|
4392
|
|
|
if( $node ){ |
4393
|
|
|
if( $visitDeeper && is_object($node) ){ |
4394
|
|
|
$node->accept($this); |
4395
|
|
|
} |
4396
|
|
|
|
4397
|
|
|
$funcName = $funcName . "Out"; |
4398
|
|
|
if( isset($this->_visitFnCache[$funcName]) ){ |
4399
|
|
|
$this->$funcName( $node ); |
4400
|
|
|
} |
4401
|
|
|
} |
4402
|
|
|
|
4403
|
|
|
}else{ |
4404
|
|
|
$node->accept($this); |
4405
|
|
|
} |
4406
|
|
|
|
4407
|
|
|
return $node; |
4408
|
|
|
} |
4409
|
|
|
|
4410
|
|
|
public function visitArray( $nodes ){ |
4411
|
|
|
|
4412
|
|
|
$newNodes = array(); |
4413
|
|
|
foreach($nodes as $node){ |
4414
|
|
|
$evald = $this->visitObj($node); |
4415
|
|
|
if( $evald ){ |
4416
|
|
|
if( is_array($evald) ){ |
4417
|
|
|
self::flatten($evald,$newNodes); |
4418
|
|
|
}else{ |
4419
|
|
|
$newNodes[] = $evald; |
4420
|
|
|
} |
4421
|
|
|
} |
4422
|
|
|
} |
4423
|
|
|
return $newNodes; |
4424
|
|
|
} |
4425
|
|
|
|
4426
|
|
|
public function flatten( $arr, &$out ){ |
4427
|
|
|
|
4428
|
|
|
foreach($arr as $item){ |
4429
|
|
|
if( !is_array($item) ){ |
4430
|
|
|
$out[] = $item; |
4431
|
|
|
continue; |
4432
|
|
|
} |
4433
|
|
|
|
4434
|
|
|
foreach($item as $nestedItem){ |
4435
|
|
|
if( is_array($nestedItem) ){ |
4436
|
|
|
self::flatten( $nestedItem, $out); |
4437
|
|
|
}else{ |
4438
|
|
|
$out[] = $nestedItem; |
4439
|
|
|
} |
4440
|
|
|
} |
4441
|
|
|
} |
4442
|
|
|
|
4443
|
|
|
return $out; |
4444
|
|
|
} |
4445
|
|
|
|
4446
|
|
|
} |
4447
|
|
|
|
4448
|
|
|
|
4449
|
|
|
|
4450
|
|
|
|
4451
|
|
|
/** |
4452
|
|
|
* Configurable |
4453
|
|
|
* |
4454
|
|
|
* @package Less |
4455
|
|
|
* @subpackage Core |
4456
|
|
|
*/ |
4457
|
|
|
abstract class Less_Configurable { |
4458
|
|
|
|
4459
|
|
|
/** |
4460
|
|
|
* Array of options |
4461
|
|
|
* |
4462
|
|
|
* @var array |
4463
|
|
|
*/ |
4464
|
|
|
protected $options = array(); |
4465
|
|
|
|
4466
|
|
|
/** |
4467
|
|
|
* Array of default options |
4468
|
|
|
* |
4469
|
|
|
* @var array |
4470
|
|
|
*/ |
4471
|
|
|
protected $defaultOptions = array(); |
4472
|
|
|
|
4473
|
|
|
|
4474
|
|
|
/** |
4475
|
|
|
* Set options |
4476
|
|
|
* |
4477
|
|
|
* If $options is an object it will be converted into an array by called |
4478
|
|
|
* it's toArray method. |
4479
|
|
|
* |
4480
|
|
|
* @throws Exception |
4481
|
|
|
* @param array|object $options |
4482
|
|
|
* |
4483
|
|
|
*/ |
4484
|
|
|
public function setOptions($options){ |
4485
|
|
|
$options = array_intersect_key($options,$this->defaultOptions); |
4486
|
|
|
$this->options = array_merge($this->defaultOptions, $this->options, $options); |
4487
|
|
|
} |
4488
|
|
|
|
4489
|
|
|
|
4490
|
|
|
/** |
4491
|
|
|
* Get an option value by name |
4492
|
|
|
* |
4493
|
|
|
* If the option is empty or not set a NULL value will be returned. |
4494
|
|
|
* |
4495
|
|
|
* @param string $name |
4496
|
|
|
* @param mixed $default Default value if confiuration of $name is not present |
4497
|
|
|
* @return mixed |
4498
|
|
|
*/ |
4499
|
|
|
public function getOption($name, $default = null){ |
4500
|
|
|
if(isset($this->options[$name])){ |
4501
|
|
|
return $this->options[$name]; |
4502
|
|
|
} |
4503
|
|
|
return $default; |
4504
|
|
|
} |
4505
|
|
|
|
4506
|
|
|
|
4507
|
|
|
/** |
4508
|
|
|
* Set an option |
4509
|
|
|
* |
4510
|
|
|
* @param string $name |
4511
|
|
|
* @param mixed $value |
4512
|
|
|
*/ |
4513
|
|
|
public function setOption($name, $value){ |
4514
|
|
|
$this->options[$name] = $value; |
4515
|
|
|
} |
4516
|
|
|
|
4517
|
|
|
} |
4518
|
|
|
|
4519
|
|
|
/** |
4520
|
|
|
* Alpha |
4521
|
|
|
* |
4522
|
|
|
* @package Less |
4523
|
|
|
* @subpackage tree |
4524
|
|
|
*/ |
4525
|
|
|
class Less_Tree_Alpha extends Less_Tree{ |
4526
|
|
|
public $value; |
4527
|
|
|
public $type = 'Alpha'; |
4528
|
|
|
|
4529
|
|
|
public function __construct($val){ |
4530
|
|
|
$this->value = $val; |
4531
|
|
|
} |
4532
|
|
|
|
4533
|
|
|
//function accept( $visitor ){ |
4534
|
|
|
// $this->value = $visitor->visit( $this->value ); |
4535
|
|
|
//} |
4536
|
|
|
|
4537
|
|
|
public function compile($env){ |
4538
|
|
|
|
4539
|
|
|
if( is_object($this->value) ){ |
4540
|
|
|
$this->value = $this->value->compile($env); |
4541
|
|
|
} |
4542
|
|
|
|
4543
|
|
|
return $this; |
4544
|
|
|
} |
4545
|
|
|
|
4546
|
|
|
/** |
4547
|
|
|
* @see Less_Tree::genCSS |
4548
|
|
|
*/ |
4549
|
|
|
public function genCSS( $output ){ |
4550
|
|
|
|
4551
|
|
|
$output->add( "alpha(opacity=" ); |
4552
|
|
|
|
4553
|
|
|
if( is_string($this->value) ){ |
4554
|
|
|
$output->add( $this->value ); |
4555
|
|
|
}else{ |
4556
|
|
|
$this->value->genCSS( $output); |
4557
|
|
|
} |
4558
|
|
|
|
4559
|
|
|
$output->add( ')' ); |
4560
|
|
|
} |
4561
|
|
|
|
4562
|
|
|
public function toCSS(){ |
4563
|
|
|
return "alpha(opacity=" . (is_string($this->value) ? $this->value : $this->value->toCSS()) . ")"; |
4564
|
|
|
} |
4565
|
|
|
|
4566
|
|
|
|
4567
|
|
|
} |
4568
|
|
|
|
4569
|
|
|
/** |
4570
|
|
|
* Anonymous |
4571
|
|
|
* |
4572
|
|
|
* @package Less |
4573
|
|
|
* @subpackage tree |
4574
|
|
|
*/ |
4575
|
|
|
class Less_Tree_Anonymous extends Less_Tree{ |
4576
|
|
|
public $value; |
4577
|
|
|
public $quote; |
4578
|
|
|
public $index; |
4579
|
|
|
public $mapLines; |
4580
|
|
|
public $currentFileInfo; |
4581
|
|
|
public $type = 'Anonymous'; |
4582
|
|
|
|
4583
|
|
|
/** |
4584
|
|
|
* @param integer $index |
4585
|
|
|
* @param boolean $mapLines |
4586
|
|
|
*/ |
4587
|
|
|
public function __construct($value, $index = null, $currentFileInfo = null, $mapLines = null ){ |
4588
|
|
|
$this->value = $value; |
4589
|
|
|
$this->index = $index; |
4590
|
|
|
$this->mapLines = $mapLines; |
4591
|
|
|
$this->currentFileInfo = $currentFileInfo; |
4592
|
|
|
} |
4593
|
|
|
|
4594
|
|
|
public function compile(){ |
4595
|
|
|
return new Less_Tree_Anonymous($this->value, $this->index, $this->currentFileInfo, $this->mapLines); |
4596
|
|
|
} |
4597
|
|
|
|
4598
|
|
View Code Duplication |
public function compare($x){ |
4599
|
|
|
if( !is_object($x) ){ |
4600
|
|
|
return -1; |
4601
|
|
|
} |
4602
|
|
|
|
4603
|
|
|
$left = $this->toCSS(); |
4604
|
|
|
$right = $x->toCSS(); |
4605
|
|
|
|
4606
|
|
|
if( $left === $right ){ |
4607
|
|
|
return 0; |
4608
|
|
|
} |
4609
|
|
|
|
4610
|
|
|
return $left < $right ? -1 : 1; |
4611
|
|
|
} |
4612
|
|
|
|
4613
|
|
|
/** |
4614
|
|
|
* @see Less_Tree::genCSS |
4615
|
|
|
*/ |
4616
|
|
|
public function genCSS( $output ){ |
4617
|
|
|
$output->add( $this->value, $this->currentFileInfo, $this->index, $this->mapLines ); |
4618
|
|
|
} |
4619
|
|
|
|
4620
|
|
|
public function toCSS(){ |
4621
|
|
|
return $this->value; |
4622
|
|
|
} |
4623
|
|
|
|
4624
|
|
|
} |
4625
|
|
|
|
4626
|
|
|
|
4627
|
|
|
/** |
4628
|
|
|
* Assignment |
4629
|
|
|
* |
4630
|
|
|
* @package Less |
4631
|
|
|
* @subpackage tree |
4632
|
|
|
*/ |
4633
|
|
|
class Less_Tree_Assignment extends Less_Tree{ |
4634
|
|
|
|
4635
|
|
|
public $key; |
4636
|
|
|
public $value; |
4637
|
|
|
public $type = 'Assignment'; |
4638
|
|
|
|
4639
|
|
|
public function __construct($key, $val) { |
4640
|
|
|
$this->key = $key; |
4641
|
|
|
$this->value = $val; |
4642
|
|
|
} |
4643
|
|
|
|
4644
|
|
|
public function accept( $visitor ){ |
4645
|
|
|
$this->value = $visitor->visitObj( $this->value ); |
4646
|
|
|
} |
4647
|
|
|
|
4648
|
|
|
public function compile($env) { |
4649
|
|
|
return new Less_Tree_Assignment( $this->key, $this->value->compile($env)); |
4650
|
|
|
} |
4651
|
|
|
|
4652
|
|
|
/** |
4653
|
|
|
* @see Less_Tree::genCSS |
4654
|
|
|
*/ |
4655
|
|
|
public function genCSS( $output ){ |
4656
|
|
|
$output->add( $this->key . '=' ); |
4657
|
|
|
$this->value->genCSS( $output ); |
4658
|
|
|
} |
4659
|
|
|
|
4660
|
|
|
public function toCss(){ |
4661
|
|
|
return $this->key . '=' . $this->value->toCSS(); |
4662
|
|
|
} |
4663
|
|
|
} |
4664
|
|
|
|
4665
|
|
|
|
4666
|
|
|
/** |
4667
|
|
|
* Attribute |
4668
|
|
|
* |
4669
|
|
|
* @package Less |
4670
|
|
|
* @subpackage tree |
4671
|
|
|
*/ |
4672
|
|
|
class Less_Tree_Attribute extends Less_Tree{ |
4673
|
|
|
|
4674
|
|
|
public $key; |
4675
|
|
|
public $op; |
4676
|
|
|
public $value; |
4677
|
|
|
public $type = 'Attribute'; |
4678
|
|
|
|
4679
|
|
|
public function __construct($key, $op, $value){ |
4680
|
|
|
$this->key = $key; |
4681
|
|
|
$this->op = $op; |
4682
|
|
|
$this->value = $value; |
4683
|
|
|
} |
4684
|
|
|
|
4685
|
|
|
public function compile($env){ |
4686
|
|
|
|
4687
|
|
|
$key_obj = is_object($this->key); |
4688
|
|
|
$val_obj = is_object($this->value); |
4689
|
|
|
|
4690
|
|
|
if( !$key_obj && !$val_obj ){ |
4691
|
|
|
return $this; |
4692
|
|
|
} |
4693
|
|
|
|
4694
|
|
|
return new Less_Tree_Attribute( |
4695
|
|
|
$key_obj ? $this->key->compile($env) : $this->key , |
4696
|
|
|
$this->op, |
4697
|
|
|
$val_obj ? $this->value->compile($env) : $this->value); |
4698
|
|
|
} |
4699
|
|
|
|
4700
|
|
|
/** |
4701
|
|
|
* @see Less_Tree::genCSS |
4702
|
|
|
*/ |
4703
|
|
|
public function genCSS( $output ){ |
4704
|
|
|
$output->add( $this->toCSS() ); |
4705
|
|
|
} |
4706
|
|
|
|
4707
|
|
|
public function toCSS(){ |
4708
|
|
|
$value = $this->key; |
4709
|
|
|
|
4710
|
|
|
if( $this->op ){ |
4711
|
|
|
$value .= $this->op; |
4712
|
|
|
$value .= (is_object($this->value) ? $this->value->toCSS() : $this->value); |
4713
|
|
|
} |
4714
|
|
|
|
4715
|
|
|
return '[' . $value . ']'; |
4716
|
|
|
} |
4717
|
|
|
} |
4718
|
|
|
|
4719
|
|
|
|
4720
|
|
|
/** |
4721
|
|
|
* Call |
4722
|
|
|
* |
4723
|
|
|
* @package Less |
4724
|
|
|
* @subpackage tree |
4725
|
|
|
*/ |
4726
|
|
|
class Less_Tree_Call extends Less_Tree{ |
4727
|
|
|
public $value; |
4728
|
|
|
|
4729
|
|
|
protected $name; |
4730
|
|
|
protected $args; |
4731
|
|
|
protected $index; |
4732
|
|
|
protected $currentFileInfo; |
4733
|
|
|
public $type = 'Call'; |
4734
|
|
|
|
4735
|
|
View Code Duplication |
public function __construct($name, $args, $index, $currentFileInfo = null ){ |
4736
|
|
|
$this->name = $name; |
4737
|
|
|
$this->args = $args; |
4738
|
|
|
$this->index = $index; |
4739
|
|
|
$this->currentFileInfo = $currentFileInfo; |
4740
|
|
|
} |
4741
|
|
|
|
4742
|
|
|
public function accept( $visitor ){ |
4743
|
|
|
$this->args = $visitor->visitArray( $this->args ); |
4744
|
|
|
} |
4745
|
|
|
|
4746
|
|
|
// |
4747
|
|
|
// When evaluating a function call, |
4748
|
|
|
// we either find the function in `tree.functions` [1], |
4749
|
|
|
// in which case we call it, passing the evaluated arguments, |
4750
|
|
|
// or we simply print it out as it appeared originally [2]. |
4751
|
|
|
// |
4752
|
|
|
// The *functions.js* file contains the built-in functions. |
4753
|
|
|
// |
4754
|
|
|
// The reason why we evaluate the arguments, is in the case where |
4755
|
|
|
// we try to pass a variable to a function, like: `saturate(@color)`. |
4756
|
|
|
// The function should receive the value, not the variable. |
4757
|
|
|
// |
4758
|
|
|
public function compile($env=null){ |
4759
|
|
|
$args = array(); |
4760
|
|
|
foreach($this->args as $a){ |
4761
|
|
|
$args[] = $a->compile($env); |
4762
|
|
|
} |
4763
|
|
|
|
4764
|
|
|
$nameLC = strtolower($this->name); |
4765
|
|
|
switch($nameLC){ |
4766
|
|
|
case '%': |
4767
|
|
|
$nameLC = '_percent'; |
4768
|
|
|
break; |
4769
|
|
|
|
4770
|
|
|
case 'get-unit': |
4771
|
|
|
$nameLC = 'getunit'; |
4772
|
|
|
break; |
4773
|
|
|
|
4774
|
|
|
case 'data-uri': |
4775
|
|
|
$nameLC = 'datauri'; |
4776
|
|
|
break; |
4777
|
|
|
|
4778
|
|
|
case 'svg-gradient': |
4779
|
|
|
$nameLC = 'svggradient'; |
4780
|
|
|
break; |
4781
|
|
|
} |
4782
|
|
|
|
4783
|
|
|
$result = null; |
4784
|
|
|
if( $nameLC === 'default' ){ |
4785
|
|
|
$result = Less_Tree_DefaultFunc::compile(); |
4786
|
|
|
|
4787
|
|
|
}else{ |
4788
|
|
|
|
4789
|
|
|
if( method_exists('Less_Functions',$nameLC) ){ // 1. |
4790
|
|
|
try { |
4791
|
|
|
|
4792
|
|
|
$func = new Less_Functions($env, $this->currentFileInfo); |
4793
|
|
|
$result = call_user_func_array( array($func,$nameLC),$args); |
4794
|
|
|
|
4795
|
|
|
} catch (Exception $e) { |
4796
|
|
|
throw new Less_Exception_Compiler('error evaluating function `' . $this->name . '` '.$e->getMessage().' index: '. $this->index); |
4797
|
|
|
} |
4798
|
|
|
} elseif( isset( $env->functions[$nameLC] ) && is_callable( $env->functions[$nameLC] ) ) { |
4799
|
|
|
try { |
4800
|
|
|
$result = call_user_func_array( $env->functions[$nameLC], $args ); |
4801
|
|
|
} catch (Exception $e) { |
4802
|
|
|
throw new Less_Exception_Compiler('error evaluating function `' . $this->name . '` '.$e->getMessage().' index: '. $this->index); |
4803
|
|
|
} |
4804
|
|
|
} |
4805
|
|
|
} |
4806
|
|
|
|
4807
|
|
|
if( $result !== null ){ |
4808
|
|
|
return $result; |
4809
|
|
|
} |
4810
|
|
|
|
4811
|
|
|
|
4812
|
|
|
return new Less_Tree_Call( $this->name, $args, $this->index, $this->currentFileInfo ); |
4813
|
|
|
} |
4814
|
|
|
|
4815
|
|
|
/** |
4816
|
|
|
* @see Less_Tree::genCSS |
4817
|
|
|
*/ |
4818
|
|
|
public function genCSS( $output ){ |
4819
|
|
|
|
4820
|
|
|
$output->add( $this->name . '(', $this->currentFileInfo, $this->index ); |
4821
|
|
|
$args_len = count($this->args); |
4822
|
|
|
for($i = 0; $i < $args_len; $i++ ){ |
4823
|
|
|
$this->args[$i]->genCSS( $output ); |
4824
|
|
|
if( $i + 1 < $args_len ){ |
4825
|
|
|
$output->add( ', ' ); |
4826
|
|
|
} |
4827
|
|
|
} |
4828
|
|
|
|
4829
|
|
|
$output->add( ')' ); |
4830
|
|
|
} |
4831
|
|
|
|
4832
|
|
|
|
4833
|
|
|
//public function toCSS(){ |
4834
|
|
|
// return $this->compile()->toCSS(); |
4835
|
|
|
//} |
4836
|
|
|
|
4837
|
|
|
} |
4838
|
|
|
|
4839
|
|
|
|
4840
|
|
|
/** |
4841
|
|
|
* Color |
4842
|
|
|
* |
4843
|
|
|
* @package Less |
4844
|
|
|
* @subpackage tree |
4845
|
|
|
*/ |
4846
|
|
|
class Less_Tree_Color extends Less_Tree{ |
4847
|
|
|
public $rgb; |
4848
|
|
|
public $alpha; |
4849
|
|
|
public $isTransparentKeyword; |
4850
|
|
|
public $type = 'Color'; |
4851
|
|
|
|
4852
|
|
|
public function __construct($rgb, $a = 1, $isTransparentKeyword = null ){ |
4853
|
|
|
|
4854
|
|
|
if( $isTransparentKeyword ){ |
4855
|
|
|
$this->rgb = $rgb; |
4856
|
|
|
$this->alpha = $a; |
4857
|
|
|
$this->isTransparentKeyword = true; |
4858
|
|
|
return; |
4859
|
|
|
} |
4860
|
|
|
|
4861
|
|
|
$this->rgb = array(); |
4862
|
|
|
if( is_array($rgb) ){ |
4863
|
|
|
$this->rgb = $rgb; |
4864
|
|
|
}else if( strlen($rgb) == 6 ){ |
4865
|
|
|
foreach(str_split($rgb, 2) as $c){ |
4866
|
|
|
$this->rgb[] = hexdec($c); |
4867
|
|
|
} |
4868
|
|
|
}else{ |
4869
|
|
|
foreach(str_split($rgb, 1) as $c){ |
4870
|
|
|
$this->rgb[] = hexdec($c.$c); |
4871
|
|
|
} |
4872
|
|
|
} |
4873
|
|
|
$this->alpha = is_numeric($a) ? $a : 1; |
4874
|
|
|
} |
4875
|
|
|
|
4876
|
|
|
public function compile(){ |
4877
|
|
|
return $this; |
4878
|
|
|
} |
4879
|
|
|
|
4880
|
|
|
public function luma(){ |
4881
|
|
|
$r = $this->rgb[0] / 255; |
4882
|
|
|
$g = $this->rgb[1] / 255; |
4883
|
|
|
$b = $this->rgb[2] / 255; |
4884
|
|
|
|
4885
|
|
|
$r = ($r <= 0.03928) ? $r / 12.92 : pow((($r + 0.055) / 1.055), 2.4); |
4886
|
|
|
$g = ($g <= 0.03928) ? $g / 12.92 : pow((($g + 0.055) / 1.055), 2.4); |
4887
|
|
|
$b = ($b <= 0.03928) ? $b / 12.92 : pow((($b + 0.055) / 1.055), 2.4); |
4888
|
|
|
|
4889
|
|
|
return 0.2126 * $r + 0.7152 * $g + 0.0722 * $b; |
4890
|
|
|
} |
4891
|
|
|
|
4892
|
|
|
/** |
4893
|
|
|
* @see Less_Tree::genCSS |
4894
|
|
|
*/ |
4895
|
|
|
public function genCSS( $output ){ |
4896
|
|
|
$output->add( $this->toCSS() ); |
4897
|
|
|
} |
4898
|
|
|
|
4899
|
|
|
public function toCSS( $doNotCompress = false ){ |
4900
|
|
|
$compress = Less_Parser::$options['compress'] && !$doNotCompress; |
4901
|
|
|
$alpha = Less_Functions::fround( $this->alpha ); |
4902
|
|
|
|
4903
|
|
|
|
4904
|
|
|
// |
4905
|
|
|
// If we have some transparency, the only way to represent it |
4906
|
|
|
// is via `rgba`. Otherwise, we use the hex representation, |
4907
|
|
|
// which has better compatibility with older browsers. |
4908
|
|
|
// Values are capped between `0` and `255`, rounded and zero-padded. |
4909
|
|
|
// |
4910
|
|
|
if( $alpha < 1 ){ |
4911
|
|
|
if( ( $alpha === 0 || $alpha === 0.0 ) && isset($this->isTransparentKeyword) && $this->isTransparentKeyword ){ |
4912
|
|
|
return 'transparent'; |
4913
|
|
|
} |
4914
|
|
|
|
4915
|
|
|
$values = array(); |
4916
|
|
|
foreach($this->rgb as $c){ |
4917
|
|
|
$values[] = Less_Functions::clamp( round($c), 255); |
4918
|
|
|
} |
4919
|
|
|
$values[] = $alpha; |
4920
|
|
|
|
4921
|
|
|
$glue = ($compress ? ',' : ', '); |
4922
|
|
|
return "rgba(" . implode($glue, $values) . ")"; |
4923
|
|
|
}else{ |
4924
|
|
|
|
4925
|
|
|
$color = $this->toRGB(); |
4926
|
|
|
|
4927
|
|
|
if( $compress ){ |
4928
|
|
|
|
4929
|
|
|
// Convert color to short format |
4930
|
|
|
if( $color[1] === $color[2] && $color[3] === $color[4] && $color[5] === $color[6]) { |
4931
|
|
|
$color = '#'.$color[1] . $color[3] . $color[5]; |
4932
|
|
|
} |
4933
|
|
|
} |
4934
|
|
|
|
4935
|
|
|
return $color; |
4936
|
|
|
} |
4937
|
|
|
} |
4938
|
|
|
|
4939
|
|
|
// |
4940
|
|
|
// Operations have to be done per-channel, if not, |
4941
|
|
|
// channels will spill onto each other. Once we have |
4942
|
|
|
// our result, in the form of an integer triplet, |
4943
|
|
|
// we create a new Color node to hold the result. |
4944
|
|
|
// |
4945
|
|
|
|
4946
|
|
|
/** |
4947
|
|
|
* @param string $op |
4948
|
|
|
*/ |
4949
|
|
|
public function operate( $op, $other) { |
4950
|
|
|
$rgb = array(); |
4951
|
|
|
$alpha = $this->alpha * (1 - $other->alpha) + $other->alpha; |
4952
|
|
|
for ($c = 0; $c < 3; $c++) { |
4953
|
|
|
$rgb[$c] = Less_Functions::operate( $op, $this->rgb[$c], $other->rgb[$c]); |
4954
|
|
|
} |
4955
|
|
|
return new Less_Tree_Color($rgb, $alpha); |
4956
|
|
|
} |
4957
|
|
|
|
4958
|
|
|
public function toRGB(){ |
4959
|
|
|
return $this->toHex($this->rgb); |
4960
|
|
|
} |
4961
|
|
|
|
4962
|
|
|
public function toHSL(){ |
4963
|
|
|
$r = $this->rgb[0] / 255; |
4964
|
|
|
$g = $this->rgb[1] / 255; |
4965
|
|
|
$b = $this->rgb[2] / 255; |
4966
|
|
|
$a = $this->alpha; |
4967
|
|
|
|
4968
|
|
|
$max = max($r, $g, $b); |
4969
|
|
|
$min = min($r, $g, $b); |
4970
|
|
|
$l = ($max + $min) / 2; |
4971
|
|
|
$d = $max - $min; |
4972
|
|
|
|
4973
|
|
|
$h = $s = 0; |
4974
|
|
|
if( $max !== $min ){ |
4975
|
|
|
$s = $l > 0.5 ? $d / (2 - $max - $min) : $d / ($max + $min); |
4976
|
|
|
|
4977
|
|
View Code Duplication |
switch ($max) { |
4978
|
|
|
case $r: $h = ($g - $b) / $d + ($g < $b ? 6 : 0); break; |
4979
|
|
|
case $g: $h = ($b - $r) / $d + 2; break; |
4980
|
|
|
case $b: $h = ($r - $g) / $d + 4; break; |
4981
|
|
|
} |
4982
|
|
|
$h /= 6; |
4983
|
|
|
} |
4984
|
|
|
return array('h' => $h * 360, 's' => $s, 'l' => $l, 'a' => $a ); |
4985
|
|
|
} |
4986
|
|
|
|
4987
|
|
|
//Adapted from http://mjijackson.com/2008/02/rgb-to-hsl-and-rgb-to-hsv-color-model-conversion-algorithms-in-javascript |
4988
|
|
|
public function toHSV() { |
4989
|
|
|
$r = $this->rgb[0] / 255; |
4990
|
|
|
$g = $this->rgb[1] / 255; |
4991
|
|
|
$b = $this->rgb[2] / 255; |
4992
|
|
|
$a = $this->alpha; |
4993
|
|
|
|
4994
|
|
|
$max = max($r, $g, $b); |
4995
|
|
|
$min = min($r, $g, $b); |
4996
|
|
|
|
4997
|
|
|
$v = $max; |
4998
|
|
|
|
4999
|
|
|
$d = $max - $min; |
5000
|
|
|
if ($max === 0) { |
5001
|
|
|
$s = 0; |
5002
|
|
|
} else { |
5003
|
|
|
$s = $d / $max; |
5004
|
|
|
} |
5005
|
|
|
|
5006
|
|
|
$h = 0; |
5007
|
|
|
if( $max !== $min ){ |
5008
|
|
View Code Duplication |
switch($max){ |
5009
|
|
|
case $r: $h = ($g - $b) / $d + ($g < $b ? 6 : 0); break; |
5010
|
|
|
case $g: $h = ($b - $r) / $d + 2; break; |
5011
|
|
|
case $b: $h = ($r - $g) / $d + 4; break; |
5012
|
|
|
} |
5013
|
|
|
$h /= 6; |
5014
|
|
|
} |
5015
|
|
|
return array('h'=> $h * 360, 's'=> $s, 'v'=> $v, 'a' => $a ); |
5016
|
|
|
} |
5017
|
|
|
|
5018
|
|
|
public function toARGB(){ |
5019
|
|
|
$argb = array_merge( (array) Less_Parser::round($this->alpha * 255), $this->rgb); |
5020
|
|
|
return $this->toHex( $argb ); |
5021
|
|
|
} |
5022
|
|
|
|
5023
|
|
|
public function compare($x){ |
5024
|
|
|
|
5025
|
|
|
if( !property_exists( $x, 'rgb' ) ){ |
5026
|
|
|
return -1; |
5027
|
|
|
} |
5028
|
|
|
|
5029
|
|
|
|
5030
|
|
|
return ($x->rgb[0] === $this->rgb[0] && |
5031
|
|
|
$x->rgb[1] === $this->rgb[1] && |
5032
|
|
|
$x->rgb[2] === $this->rgb[2] && |
5033
|
|
|
$x->alpha === $this->alpha) ? 0 : -1; |
5034
|
|
|
} |
5035
|
|
|
|
5036
|
|
|
public function toHex( $v ){ |
5037
|
|
|
|
5038
|
|
|
$ret = '#'; |
5039
|
|
|
foreach($v as $c){ |
5040
|
|
|
$c = Less_Functions::clamp( Less_Parser::round($c), 255); |
5041
|
|
|
if( $c < 16 ){ |
5042
|
|
|
$ret .= '0'; |
5043
|
|
|
} |
5044
|
|
|
$ret .= dechex($c); |
5045
|
|
|
} |
5046
|
|
|
|
5047
|
|
|
return $ret; |
5048
|
|
|
} |
5049
|
|
|
|
5050
|
|
|
|
5051
|
|
|
/** |
5052
|
|
|
* @param string $keyword |
5053
|
|
|
*/ |
5054
|
|
|
public static function fromKeyword( $keyword ){ |
5055
|
|
|
$keyword = strtolower($keyword); |
5056
|
|
|
|
5057
|
|
|
if( Less_Colors::hasOwnProperty($keyword) ){ |
5058
|
|
|
// detect named color |
5059
|
|
|
return new Less_Tree_Color(substr(Less_Colors::color($keyword), 1)); |
5060
|
|
|
} |
5061
|
|
|
|
5062
|
|
|
if( $keyword === 'transparent' ){ |
5063
|
|
|
return new Less_Tree_Color( array(0, 0, 0), 0, true); |
5064
|
|
|
} |
5065
|
|
|
} |
5066
|
|
|
|
5067
|
|
|
} |
5068
|
|
|
|
5069
|
|
|
|
5070
|
|
|
/** |
5071
|
|
|
* Comment |
5072
|
|
|
* |
5073
|
|
|
* @package Less |
5074
|
|
|
* @subpackage tree |
5075
|
|
|
*/ |
5076
|
|
|
class Less_Tree_Comment extends Less_Tree{ |
5077
|
|
|
|
5078
|
|
|
public $value; |
5079
|
|
|
public $silent; |
5080
|
|
|
public $isReferenced; |
5081
|
|
|
public $currentFileInfo; |
5082
|
|
|
public $type = 'Comment'; |
5083
|
|
|
|
5084
|
|
|
public function __construct($value, $silent, $index = null, $currentFileInfo = null ){ |
5085
|
|
|
$this->value = $value; |
5086
|
|
|
$this->silent = !! $silent; |
5087
|
|
|
$this->currentFileInfo = $currentFileInfo; |
5088
|
|
|
} |
5089
|
|
|
|
5090
|
|
|
/** |
5091
|
|
|
* @see Less_Tree::genCSS |
5092
|
|
|
*/ |
5093
|
|
|
public function genCSS( $output ){ |
5094
|
|
|
//if( $this->debugInfo ){ |
5095
|
|
|
//$output->add( tree.debugInfo($env, $this), $this->currentFileInfo, $this->index); |
5096
|
|
|
//} |
5097
|
|
|
$output->add( trim($this->value) );//TODO shouldn't need to trim, we shouldn't grab the \n |
5098
|
|
|
} |
5099
|
|
|
|
5100
|
|
|
public function toCSS(){ |
5101
|
|
|
return Less_Parser::$options['compress'] ? '' : $this->value; |
5102
|
|
|
} |
5103
|
|
|
|
5104
|
|
|
public function isSilent(){ |
5105
|
|
|
$isReference = ($this->currentFileInfo && isset($this->currentFileInfo['reference']) && (!isset($this->isReferenced) || !$this->isReferenced) ); |
5106
|
|
|
$isCompressed = Less_Parser::$options['compress'] && !preg_match('/^\/\*!/', $this->value); |
5107
|
|
|
return $this->silent || $isReference || $isCompressed; |
5108
|
|
|
} |
5109
|
|
|
|
5110
|
|
|
public function compile(){ |
5111
|
|
|
return $this; |
5112
|
|
|
} |
5113
|
|
|
|
5114
|
|
|
public function markReferenced(){ |
5115
|
|
|
$this->isReferenced = true; |
5116
|
|
|
} |
5117
|
|
|
|
5118
|
|
|
} |
5119
|
|
|
|
5120
|
|
|
|
5121
|
|
|
/** |
5122
|
|
|
* Condition |
5123
|
|
|
* |
5124
|
|
|
* @package Less |
5125
|
|
|
* @subpackage tree |
5126
|
|
|
*/ |
5127
|
|
|
class Less_Tree_Condition extends Less_Tree{ |
5128
|
|
|
|
5129
|
|
|
public $op; |
5130
|
|
|
public $lvalue; |
5131
|
|
|
public $rvalue; |
5132
|
|
|
public $index; |
5133
|
|
|
public $negate; |
5134
|
|
|
public $type = 'Condition'; |
5135
|
|
|
|
5136
|
|
|
public function __construct($op, $l, $r, $i = 0, $negate = false) { |
5137
|
|
|
$this->op = trim($op); |
5138
|
|
|
$this->lvalue = $l; |
5139
|
|
|
$this->rvalue = $r; |
5140
|
|
|
$this->index = $i; |
5141
|
|
|
$this->negate = $negate; |
5142
|
|
|
} |
5143
|
|
|
|
5144
|
|
|
public function accept($visitor){ |
5145
|
|
|
$this->lvalue = $visitor->visitObj( $this->lvalue ); |
5146
|
|
|
$this->rvalue = $visitor->visitObj( $this->rvalue ); |
5147
|
|
|
} |
5148
|
|
|
|
5149
|
|
|
public function compile($env) { |
5150
|
|
|
$a = $this->lvalue->compile($env); |
5151
|
|
|
$b = $this->rvalue->compile($env); |
5152
|
|
|
|
5153
|
|
|
switch( $this->op ){ |
5154
|
|
|
case 'and': |
5155
|
|
|
$result = $a && $b; |
5156
|
|
|
break; |
5157
|
|
|
|
5158
|
|
|
case 'or': |
5159
|
|
|
$result = $a || $b; |
5160
|
|
|
break; |
5161
|
|
|
|
5162
|
|
|
default: |
5163
|
|
|
if( Less_Parser::is_method($a, 'compare') ){ |
5164
|
|
|
$result = $a->compare($b); |
5165
|
|
|
}elseif( Less_Parser::is_method($b, 'compare') ){ |
5166
|
|
|
$result = $b->compare($a); |
5167
|
|
|
}else{ |
5168
|
|
|
throw new Less_Exception_Compiler('Unable to perform comparison', null, $this->index); |
5169
|
|
|
} |
5170
|
|
|
|
5171
|
|
|
switch ($result) { |
5172
|
|
View Code Duplication |
case -1: |
5173
|
|
|
$result = $this->op === '<' || $this->op === '=<' || $this->op === '<='; |
5174
|
|
|
break; |
5175
|
|
|
|
5176
|
|
View Code Duplication |
case 0: |
5177
|
|
|
$result = $this->op === '=' || $this->op === '>=' || $this->op === '=<' || $this->op === '<='; |
5178
|
|
|
break; |
5179
|
|
|
|
5180
|
|
|
case 1: |
5181
|
|
|
$result = $this->op === '>' || $this->op === '>='; |
5182
|
|
|
break; |
5183
|
|
|
} |
5184
|
|
|
break; |
5185
|
|
|
} |
5186
|
|
|
|
5187
|
|
|
return $this->negate ? !$result : $result; |
5188
|
|
|
} |
5189
|
|
|
|
5190
|
|
|
} |
5191
|
|
|
|
5192
|
|
|
|
5193
|
|
|
/** |
5194
|
|
|
* DefaultFunc |
5195
|
|
|
* |
5196
|
|
|
* @package Less |
5197
|
|
|
* @subpackage tree |
5198
|
|
|
*/ |
5199
|
|
|
class Less_Tree_DefaultFunc{ |
5200
|
|
|
|
5201
|
|
|
static $error_; |
5202
|
|
|
static $value_; |
5203
|
|
|
|
5204
|
|
|
public static function compile(){ |
5205
|
|
|
if( self::$error_ ){ |
5206
|
|
|
throw new Exception(self::$error_); |
5207
|
|
|
} |
5208
|
|
|
if( self::$value_ !== null ){ |
5209
|
|
|
return self::$value_ ? new Less_Tree_Keyword('true') : new Less_Tree_Keyword('false'); |
5210
|
|
|
} |
5211
|
|
|
} |
5212
|
|
|
|
5213
|
|
|
public static function value( $v ){ |
5214
|
|
|
self::$value_ = $v; |
5215
|
|
|
} |
5216
|
|
|
|
5217
|
|
|
public static function error( $e ){ |
5218
|
|
|
self::$error_ = $e; |
5219
|
|
|
} |
5220
|
|
|
|
5221
|
|
|
public static function reset(){ |
5222
|
|
|
self::$value_ = self::$error_ = null; |
5223
|
|
|
} |
5224
|
|
|
} |
5225
|
|
|
|
5226
|
|
|
/** |
5227
|
|
|
* DetachedRuleset |
5228
|
|
|
* |
5229
|
|
|
* @package Less |
5230
|
|
|
* @subpackage tree |
5231
|
|
|
*/ |
5232
|
|
|
class Less_Tree_DetachedRuleset extends Less_Tree{ |
5233
|
|
|
|
5234
|
|
|
public $ruleset; |
5235
|
|
|
public $frames; |
5236
|
|
|
public $type = 'DetachedRuleset'; |
5237
|
|
|
|
5238
|
|
|
public function __construct( $ruleset, $frames = null ){ |
5239
|
|
|
$this->ruleset = $ruleset; |
5240
|
|
|
$this->frames = $frames; |
5241
|
|
|
} |
5242
|
|
|
|
5243
|
|
|
public function accept($visitor) { |
5244
|
|
|
$this->ruleset = $visitor->visitObj($this->ruleset); |
5245
|
|
|
} |
5246
|
|
|
|
5247
|
|
|
public function compile($env){ |
5248
|
|
|
if( $this->frames ){ |
5249
|
|
|
$frames = $this->frames; |
5250
|
|
|
}else{ |
5251
|
|
|
$frames = $env->frames; |
5252
|
|
|
} |
5253
|
|
|
return new Less_Tree_DetachedRuleset($this->ruleset, $frames); |
5254
|
|
|
} |
5255
|
|
|
|
5256
|
|
|
public function callEval($env) { |
5257
|
|
|
if( $this->frames ){ |
5258
|
|
|
return $this->ruleset->compile( $env->copyEvalEnv( array_merge($this->frames,$env->frames) ) ); |
5259
|
|
|
} |
5260
|
|
|
return $this->ruleset->compile( $env ); |
5261
|
|
|
} |
5262
|
|
|
} |
5263
|
|
|
|
5264
|
|
|
|
5265
|
|
|
|
5266
|
|
|
/** |
5267
|
|
|
* Dimension |
5268
|
|
|
* |
5269
|
|
|
* @package Less |
5270
|
|
|
* @subpackage tree |
5271
|
|
|
*/ |
5272
|
|
|
class Less_Tree_Dimension extends Less_Tree{ |
5273
|
|
|
|
5274
|
|
|
public $value; |
5275
|
|
|
public $unit; |
5276
|
|
|
public $type = 'Dimension'; |
5277
|
|
|
|
5278
|
|
|
public function __construct($value, $unit = null){ |
5279
|
|
|
$this->value = floatval($value); |
5280
|
|
|
|
5281
|
|
|
if( $unit && ($unit instanceof Less_Tree_Unit) ){ |
5282
|
|
|
$this->unit = $unit; |
5283
|
|
|
}elseif( $unit ){ |
5284
|
|
|
$this->unit = new Less_Tree_Unit( array($unit) ); |
5285
|
|
|
}else{ |
5286
|
|
|
$this->unit = new Less_Tree_Unit( ); |
5287
|
|
|
} |
5288
|
|
|
} |
5289
|
|
|
|
5290
|
|
|
public function accept( $visitor ){ |
5291
|
|
|
$this->unit = $visitor->visitObj( $this->unit ); |
5292
|
|
|
} |
5293
|
|
|
|
5294
|
|
|
public function compile(){ |
5295
|
|
|
return $this; |
5296
|
|
|
} |
5297
|
|
|
|
5298
|
|
|
public function toColor() { |
5299
|
|
|
return new Less_Tree_Color(array($this->value, $this->value, $this->value)); |
5300
|
|
|
} |
5301
|
|
|
|
5302
|
|
|
/** |
5303
|
|
|
* @see Less_Tree::genCSS |
5304
|
|
|
*/ |
5305
|
|
|
public function genCSS( $output ){ |
5306
|
|
|
|
5307
|
|
|
if( Less_Parser::$options['strictUnits'] && !$this->unit->isSingular() ){ |
5308
|
|
|
throw new Less_Exception_Compiler("Multiple units in dimension. Correct the units or use the unit function. Bad unit: ".$this->unit->toString()); |
5309
|
|
|
} |
5310
|
|
|
|
5311
|
|
|
$value = Less_Functions::fround( $this->value ); |
5312
|
|
|
$strValue = (string)$value; |
5313
|
|
|
|
5314
|
|
|
if( $value !== 0 && $value < 0.000001 && $value > -0.000001 ){ |
5315
|
|
|
// would be output 1e-6 etc. |
5316
|
|
|
$strValue = number_format($strValue,10); |
5317
|
|
|
$strValue = preg_replace('/\.?0+$/','', $strValue); |
5318
|
|
|
} |
5319
|
|
|
|
5320
|
|
|
if( Less_Parser::$options['compress'] ){ |
5321
|
|
|
// Zero values doesn't need a unit |
5322
|
|
|
if( $value === 0 && $this->unit->isLength() ){ |
5323
|
|
|
$output->add( $strValue ); |
5324
|
|
|
return $strValue; |
5325
|
|
|
} |
5326
|
|
|
|
5327
|
|
|
// Float values doesn't need a leading zero |
5328
|
|
|
if( $value > 0 && $value < 1 && $strValue[0] === '0' ){ |
5329
|
|
|
$strValue = substr($strValue,1); |
5330
|
|
|
} |
5331
|
|
|
} |
5332
|
|
|
|
5333
|
|
|
$output->add( $strValue ); |
5334
|
|
|
$this->unit->genCSS( $output ); |
5335
|
|
|
} |
5336
|
|
|
|
5337
|
|
|
public function __toString(){ |
5338
|
|
|
return $this->toCSS(); |
5339
|
|
|
} |
5340
|
|
|
|
5341
|
|
|
// In an operation between two Dimensions, |
5342
|
|
|
// we default to the first Dimension's unit, |
5343
|
|
|
// so `1px + 2em` will yield `3px`. |
5344
|
|
|
|
5345
|
|
|
/** |
5346
|
|
|
* @param string $op |
5347
|
|
|
*/ |
5348
|
|
|
public function operate( $op, $other){ |
5349
|
|
|
|
5350
|
|
|
$value = Less_Functions::operate( $op, $this->value, $other->value); |
5351
|
|
|
$unit = clone $this->unit; |
5352
|
|
|
|
5353
|
|
|
if( $op === '+' || $op === '-' ){ |
5354
|
|
|
|
5355
|
|
|
if( !$unit->numerator && !$unit->denominator ){ |
|
|
|
|
5356
|
|
|
$unit->numerator = $other->unit->numerator; |
5357
|
|
|
$unit->denominator = $other->unit->denominator; |
5358
|
|
|
}elseif( !$other->unit->numerator && !$other->unit->denominator ){ |
5359
|
|
|
// do nothing |
5360
|
|
|
}else{ |
5361
|
|
|
$other = $other->convertTo( $this->unit->usedUnits()); |
5362
|
|
|
|
5363
|
|
|
if( Less_Parser::$options['strictUnits'] && $other->unit->toString() !== $unit->toCSS() ){ |
5364
|
|
|
throw new Less_Exception_Compiler("Incompatible units. Change the units or use the unit function. Bad units: '".$unit->toString() . "' and ".$other->unit->toString()+"'."); |
5365
|
|
|
} |
5366
|
|
|
|
5367
|
|
|
$value = Less_Functions::operate( $op, $this->value, $other->value); |
5368
|
|
|
} |
5369
|
|
|
}elseif( $op === '*' ){ |
5370
|
|
|
$unit->numerator = array_merge($unit->numerator, $other->unit->numerator); |
5371
|
|
|
$unit->denominator = array_merge($unit->denominator, $other->unit->denominator); |
5372
|
|
|
sort($unit->numerator); |
5373
|
|
|
sort($unit->denominator); |
5374
|
|
|
$unit->cancel(); |
5375
|
|
|
}elseif( $op === '/' ){ |
5376
|
|
|
$unit->numerator = array_merge($unit->numerator, $other->unit->denominator); |
5377
|
|
|
$unit->denominator = array_merge($unit->denominator, $other->unit->numerator); |
5378
|
|
|
sort($unit->numerator); |
5379
|
|
|
sort($unit->denominator); |
5380
|
|
|
$unit->cancel(); |
5381
|
|
|
} |
5382
|
|
|
return new Less_Tree_Dimension( $value, $unit); |
5383
|
|
|
} |
5384
|
|
|
|
5385
|
|
|
public function compare($other) { |
5386
|
|
|
if ($other instanceof Less_Tree_Dimension) { |
5387
|
|
|
|
5388
|
|
|
if( $this->unit->isEmpty() || $other->unit->isEmpty() ){ |
5389
|
|
|
$a = $this; |
5390
|
|
|
$b = $other; |
5391
|
|
|
} else { |
5392
|
|
|
$a = $this->unify(); |
5393
|
|
|
$b = $other->unify(); |
5394
|
|
|
if( $a->unit->compare($b->unit) !== 0 ){ |
5395
|
|
|
return -1; |
5396
|
|
|
} |
5397
|
|
|
} |
5398
|
|
|
$aValue = $a->value; |
5399
|
|
|
$bValue = $b->value; |
5400
|
|
|
|
5401
|
|
|
if ($bValue > $aValue) { |
5402
|
|
|
return -1; |
5403
|
|
|
} elseif ($bValue < $aValue) { |
5404
|
|
|
return 1; |
5405
|
|
|
} else { |
5406
|
|
|
return 0; |
5407
|
|
|
} |
5408
|
|
|
} else { |
5409
|
|
|
return -1; |
5410
|
|
|
} |
5411
|
|
|
} |
5412
|
|
|
|
5413
|
|
|
public function unify() { |
5414
|
|
|
return $this->convertTo(array('length'=> 'px', 'duration'=> 's', 'angle' => 'rad' )); |
5415
|
|
|
} |
5416
|
|
|
|
5417
|
|
|
public function convertTo($conversions) { |
5418
|
|
|
$value = $this->value; |
5419
|
|
|
$unit = clone $this->unit; |
5420
|
|
|
|
5421
|
|
|
if( is_string($conversions) ){ |
5422
|
|
|
$derivedConversions = array(); |
5423
|
|
|
foreach( Less_Tree_UnitConversions::$groups as $i ){ |
5424
|
|
|
if( isset(Less_Tree_UnitConversions::${$i}[$conversions]) ){ |
5425
|
|
|
$derivedConversions = array( $i => $conversions); |
5426
|
|
|
} |
5427
|
|
|
} |
5428
|
|
|
$conversions = $derivedConversions; |
5429
|
|
|
} |
5430
|
|
|
|
5431
|
|
|
|
5432
|
|
|
foreach($conversions as $groupName => $targetUnit){ |
5433
|
|
|
$group = Less_Tree_UnitConversions::${$groupName}; |
5434
|
|
|
|
5435
|
|
|
//numerator |
5436
|
|
View Code Duplication |
foreach($unit->numerator as $i => $atomicUnit){ |
5437
|
|
|
$atomicUnit = $unit->numerator[$i]; |
5438
|
|
|
if( !isset($group[$atomicUnit]) ){ |
5439
|
|
|
continue; |
5440
|
|
|
} |
5441
|
|
|
|
5442
|
|
|
$value = $value * ($group[$atomicUnit] / $group[$targetUnit]); |
5443
|
|
|
|
5444
|
|
|
$unit->numerator[$i] = $targetUnit; |
5445
|
|
|
} |
5446
|
|
|
|
5447
|
|
|
//denominator |
5448
|
|
View Code Duplication |
foreach($unit->denominator as $i => $atomicUnit){ |
5449
|
|
|
$atomicUnit = $unit->denominator[$i]; |
5450
|
|
|
if( !isset($group[$atomicUnit]) ){ |
5451
|
|
|
continue; |
5452
|
|
|
} |
5453
|
|
|
|
5454
|
|
|
$value = $value / ($group[$atomicUnit] / $group[$targetUnit]); |
5455
|
|
|
|
5456
|
|
|
$unit->denominator[$i] = $targetUnit; |
5457
|
|
|
} |
5458
|
|
|
} |
5459
|
|
|
|
5460
|
|
|
$unit->cancel(); |
5461
|
|
|
|
5462
|
|
|
return new Less_Tree_Dimension( $value, $unit); |
5463
|
|
|
} |
5464
|
|
|
} |
5465
|
|
|
|
5466
|
|
|
|
5467
|
|
|
/** |
5468
|
|
|
* Directive |
5469
|
|
|
* |
5470
|
|
|
* @package Less |
5471
|
|
|
* @subpackage tree |
5472
|
|
|
*/ |
5473
|
|
|
class Less_Tree_Directive extends Less_Tree{ |
5474
|
|
|
|
5475
|
|
|
public $name; |
5476
|
|
|
public $value; |
5477
|
|
|
public $rules; |
5478
|
|
|
public $index; |
5479
|
|
|
public $isReferenced; |
5480
|
|
|
public $currentFileInfo; |
5481
|
|
|
public $debugInfo; |
5482
|
|
|
public $type = 'Directive'; |
5483
|
|
|
|
5484
|
|
|
public function __construct($name, $value = null, $rules, $index = null, $currentFileInfo = null, $debugInfo = null ){ |
5485
|
|
|
$this->name = $name; |
5486
|
|
|
$this->value = $value; |
5487
|
|
|
if( $rules ){ |
5488
|
|
|
$this->rules = $rules; |
5489
|
|
|
$this->rules->allowImports = true; |
5490
|
|
|
} |
5491
|
|
|
|
5492
|
|
|
$this->index = $index; |
5493
|
|
|
$this->currentFileInfo = $currentFileInfo; |
5494
|
|
|
$this->debugInfo = $debugInfo; |
5495
|
|
|
} |
5496
|
|
|
|
5497
|
|
|
|
5498
|
|
|
public function accept( $visitor ){ |
5499
|
|
|
if( $this->rules ){ |
5500
|
|
|
$this->rules = $visitor->visitObj( $this->rules ); |
5501
|
|
|
} |
5502
|
|
|
if( $this->value ){ |
5503
|
|
|
$this->value = $visitor->visitObj( $this->value ); |
5504
|
|
|
} |
5505
|
|
|
} |
5506
|
|
|
|
5507
|
|
|
|
5508
|
|
|
/** |
5509
|
|
|
* @see Less_Tree::genCSS |
5510
|
|
|
*/ |
5511
|
|
|
public function genCSS( $output ){ |
5512
|
|
|
$value = $this->value; |
5513
|
|
|
$rules = $this->rules; |
5514
|
|
|
$output->add( $this->name, $this->currentFileInfo, $this->index ); |
5515
|
|
|
if( $this->value ){ |
5516
|
|
|
$output->add(' '); |
5517
|
|
|
$this->value->genCSS($output); |
5518
|
|
|
} |
5519
|
|
|
if( $this->rules ){ |
5520
|
|
|
Less_Tree::outputRuleset( $output, array($this->rules)); |
5521
|
|
|
} else { |
5522
|
|
|
$output->add(';'); |
5523
|
|
|
} |
5524
|
|
|
} |
5525
|
|
|
|
5526
|
|
|
public function compile($env){ |
5527
|
|
|
|
5528
|
|
|
$value = $this->value; |
5529
|
|
|
$rules = $this->rules; |
5530
|
|
|
if( $value ){ |
5531
|
|
|
$value = $value->compile($env); |
5532
|
|
|
} |
5533
|
|
|
|
5534
|
|
|
if( $rules ){ |
5535
|
|
|
$rules = $rules->compile($env); |
5536
|
|
|
$rules->root = true; |
5537
|
|
|
} |
5538
|
|
|
|
5539
|
|
|
return new Less_Tree_Directive( $this->name, $value, $rules, $this->index, $this->currentFileInfo, $this->debugInfo ); |
5540
|
|
|
} |
5541
|
|
|
|
5542
|
|
|
|
5543
|
|
|
public function variable($name){ |
5544
|
|
|
if( $this->rules ){ |
5545
|
|
|
return $this->rules->variable($name); |
5546
|
|
|
} |
5547
|
|
|
} |
5548
|
|
|
|
5549
|
|
|
public function find($selector){ |
5550
|
|
|
if( $this->rules ){ |
5551
|
|
|
return $this->rules->find($selector, $this); |
5552
|
|
|
} |
5553
|
|
|
} |
5554
|
|
|
|
5555
|
|
|
//rulesets: function () { if (this.rules) return tree.Ruleset.prototype.rulesets.apply(this.rules); }, |
5556
|
|
|
|
5557
|
|
|
public function markReferenced(){ |
5558
|
|
|
$this->isReferenced = true; |
5559
|
|
|
if( $this->rules ){ |
5560
|
|
|
Less_Tree::ReferencedArray($this->rules->rules); |
5561
|
|
|
} |
5562
|
|
|
} |
5563
|
|
|
|
5564
|
|
|
} |
5565
|
|
|
|
5566
|
|
|
|
5567
|
|
|
/** |
5568
|
|
|
* Element |
5569
|
|
|
* |
5570
|
|
|
* @package Less |
5571
|
|
|
* @subpackage tree |
5572
|
|
|
*/ |
5573
|
|
|
class Less_Tree_Element extends Less_Tree{ |
5574
|
|
|
|
5575
|
|
|
public $combinator = ''; |
5576
|
|
|
public $value = ''; |
5577
|
|
|
public $index; |
5578
|
|
|
public $currentFileInfo; |
5579
|
|
|
public $type = 'Element'; |
5580
|
|
|
|
5581
|
|
|
public $value_is_object = false; |
5582
|
|
|
|
5583
|
|
|
public function __construct($combinator, $value, $index = null, $currentFileInfo = null ){ |
5584
|
|
|
|
5585
|
|
|
$this->value = $value; |
5586
|
|
|
$this->value_is_object = is_object($value); |
5587
|
|
|
|
5588
|
|
|
if( $combinator ){ |
5589
|
|
|
$this->combinator = $combinator; |
5590
|
|
|
} |
5591
|
|
|
|
5592
|
|
|
$this->index = $index; |
5593
|
|
|
$this->currentFileInfo = $currentFileInfo; |
5594
|
|
|
} |
5595
|
|
|
|
5596
|
|
|
public function accept( $visitor ){ |
5597
|
|
|
if( $this->value_is_object ){ //object or string |
5598
|
|
|
$this->value = $visitor->visitObj( $this->value ); |
5599
|
|
|
} |
5600
|
|
|
} |
5601
|
|
|
|
5602
|
|
|
public function compile($env){ |
5603
|
|
|
|
5604
|
|
|
if( Less_Environment::$mixin_stack ){ |
5605
|
|
|
return new Less_Tree_Element($this->combinator, ($this->value_is_object ? $this->value->compile($env) : $this->value), $this->index, $this->currentFileInfo ); |
5606
|
|
|
} |
5607
|
|
|
|
5608
|
|
|
if( $this->value_is_object ){ |
5609
|
|
|
$this->value = $this->value->compile($env); |
5610
|
|
|
} |
5611
|
|
|
|
5612
|
|
|
return $this; |
5613
|
|
|
} |
5614
|
|
|
|
5615
|
|
|
/** |
5616
|
|
|
* @see Less_Tree::genCSS |
5617
|
|
|
*/ |
5618
|
|
|
public function genCSS( $output ){ |
5619
|
|
|
$output->add( $this->toCSS(), $this->currentFileInfo, $this->index ); |
5620
|
|
|
} |
5621
|
|
|
|
5622
|
|
|
public function toCSS(){ |
5623
|
|
|
|
5624
|
|
|
if( $this->value_is_object ){ |
5625
|
|
|
$value = $this->value->toCSS(); |
5626
|
|
|
}else{ |
5627
|
|
|
$value = $this->value; |
5628
|
|
|
} |
5629
|
|
|
|
5630
|
|
|
|
5631
|
|
|
if( $value === '' && $this->combinator && $this->combinator === '&' ){ |
5632
|
|
|
return ''; |
5633
|
|
|
} |
5634
|
|
|
|
5635
|
|
|
|
5636
|
|
|
return Less_Environment::$_outputMap[$this->combinator] . $value; |
5637
|
|
|
} |
5638
|
|
|
|
5639
|
|
|
} |
5640
|
|
|
|
5641
|
|
|
|
5642
|
|
|
/** |
5643
|
|
|
* Expression |
5644
|
|
|
* |
5645
|
|
|
* @package Less |
5646
|
|
|
* @subpackage tree |
5647
|
|
|
*/ |
5648
|
|
|
class Less_Tree_Expression extends Less_Tree{ |
5649
|
|
|
|
5650
|
|
|
public $value = array(); |
5651
|
|
|
public $parens = false; |
5652
|
|
|
public $parensInOp = false; |
5653
|
|
|
public $type = 'Expression'; |
5654
|
|
|
|
5655
|
|
|
public function __construct( $value, $parens = null ){ |
5656
|
|
|
$this->value = $value; |
5657
|
|
|
$this->parens = $parens; |
5658
|
|
|
} |
5659
|
|
|
|
5660
|
|
|
public function accept( $visitor ){ |
5661
|
|
|
$this->value = $visitor->visitArray( $this->value ); |
5662
|
|
|
} |
5663
|
|
|
|
5664
|
|
|
public function compile($env) { |
5665
|
|
|
|
5666
|
|
|
$doubleParen = false; |
5667
|
|
|
|
5668
|
|
|
if( $this->parens && !$this->parensInOp ){ |
5669
|
|
|
Less_Environment::$parensStack++; |
5670
|
|
|
} |
5671
|
|
|
|
5672
|
|
|
$returnValue = null; |
5673
|
|
|
if( $this->value ){ |
|
|
|
|
5674
|
|
|
|
5675
|
|
|
$count = count($this->value); |
5676
|
|
|
|
5677
|
|
|
if( $count > 1 ){ |
5678
|
|
|
|
5679
|
|
|
$ret = array(); |
5680
|
|
|
foreach($this->value as $e){ |
5681
|
|
|
$ret[] = $e->compile($env); |
5682
|
|
|
} |
5683
|
|
|
$returnValue = new Less_Tree_Expression($ret); |
5684
|
|
|
|
5685
|
|
|
}else{ |
5686
|
|
|
|
5687
|
|
|
if( ($this->value[0] instanceof Less_Tree_Expression) && $this->value[0]->parens && !$this->value[0]->parensInOp ){ |
5688
|
|
|
$doubleParen = true; |
5689
|
|
|
} |
5690
|
|
|
|
5691
|
|
|
$returnValue = $this->value[0]->compile($env); |
5692
|
|
|
} |
5693
|
|
|
|
5694
|
|
|
} else { |
5695
|
|
|
$returnValue = $this; |
5696
|
|
|
} |
5697
|
|
|
|
5698
|
|
|
if( $this->parens ){ |
5699
|
|
|
if( !$this->parensInOp ){ |
5700
|
|
|
Less_Environment::$parensStack--; |
5701
|
|
|
|
5702
|
|
|
}elseif( !Less_Environment::isMathOn() && !$doubleParen ){ |
5703
|
|
|
$returnValue = new Less_Tree_Paren($returnValue); |
5704
|
|
|
|
5705
|
|
|
} |
5706
|
|
|
} |
5707
|
|
|
return $returnValue; |
5708
|
|
|
} |
5709
|
|
|
|
5710
|
|
|
/** |
5711
|
|
|
* @see Less_Tree::genCSS |
5712
|
|
|
*/ |
5713
|
|
View Code Duplication |
public function genCSS( $output ){ |
5714
|
|
|
$val_len = count($this->value); |
5715
|
|
|
for( $i = 0; $i < $val_len; $i++ ){ |
5716
|
|
|
$this->value[$i]->genCSS( $output ); |
5717
|
|
|
if( $i + 1 < $val_len ){ |
5718
|
|
|
$output->add( ' ' ); |
5719
|
|
|
} |
5720
|
|
|
} |
5721
|
|
|
} |
5722
|
|
|
|
5723
|
|
|
public function throwAwayComments() { |
5724
|
|
|
|
5725
|
|
|
if( is_array($this->value) ){ |
5726
|
|
|
$new_value = array(); |
5727
|
|
|
foreach($this->value as $v){ |
5728
|
|
|
if( $v instanceof Less_Tree_Comment ){ |
5729
|
|
|
continue; |
5730
|
|
|
} |
5731
|
|
|
$new_value[] = $v; |
5732
|
|
|
} |
5733
|
|
|
$this->value = $new_value; |
5734
|
|
|
} |
5735
|
|
|
} |
5736
|
|
|
} |
5737
|
|
|
|
5738
|
|
|
|
5739
|
|
|
/** |
5740
|
|
|
* Extend |
5741
|
|
|
* |
5742
|
|
|
* @package Less |
5743
|
|
|
* @subpackage tree |
5744
|
|
|
*/ |
5745
|
|
|
class Less_Tree_Extend extends Less_Tree{ |
5746
|
|
|
|
5747
|
|
|
public $selector; |
5748
|
|
|
public $option; |
5749
|
|
|
public $index; |
5750
|
|
|
public $selfSelectors = array(); |
5751
|
|
|
public $allowBefore; |
5752
|
|
|
public $allowAfter; |
5753
|
|
|
public $firstExtendOnThisSelectorPath; |
5754
|
|
|
public $type = 'Extend'; |
5755
|
|
|
public $ruleset; |
5756
|
|
|
|
5757
|
|
|
|
5758
|
|
|
public $object_id; |
5759
|
|
|
public $parent_ids = array(); |
5760
|
|
|
|
5761
|
|
|
/** |
5762
|
|
|
* @param integer $index |
5763
|
|
|
*/ |
5764
|
|
|
public function __construct($selector, $option, $index){ |
5765
|
|
|
static $i = 0; |
5766
|
|
|
$this->selector = $selector; |
5767
|
|
|
$this->option = $option; |
5768
|
|
|
$this->index = $index; |
5769
|
|
|
|
5770
|
|
|
switch($option){ |
5771
|
|
|
case "all": |
5772
|
|
|
$this->allowBefore = true; |
5773
|
|
|
$this->allowAfter = true; |
5774
|
|
|
break; |
5775
|
|
|
default: |
5776
|
|
|
$this->allowBefore = false; |
5777
|
|
|
$this->allowAfter = false; |
5778
|
|
|
break; |
5779
|
|
|
} |
5780
|
|
|
|
5781
|
|
|
$this->object_id = $i++; |
5782
|
|
|
$this->parent_ids = array($this->object_id); |
5783
|
|
|
} |
5784
|
|
|
|
5785
|
|
|
public function accept( $visitor ){ |
5786
|
|
|
$this->selector = $visitor->visitObj( $this->selector ); |
5787
|
|
|
} |
5788
|
|
|
|
5789
|
|
|
public function compile( $env ){ |
5790
|
|
|
Less_Parser::$has_extends = true; |
5791
|
|
|
$this->selector = $this->selector->compile($env); |
5792
|
|
|
return $this; |
5793
|
|
|
//return new Less_Tree_Extend( $this->selector->compile($env), $this->option, $this->index); |
5794
|
|
|
} |
5795
|
|
|
|
5796
|
|
|
public function findSelfSelectors( $selectors ){ |
5797
|
|
|
$selfElements = array(); |
5798
|
|
|
|
5799
|
|
|
|
5800
|
|
|
for( $i = 0, $selectors_len = count($selectors); $i < $selectors_len; $i++ ){ |
5801
|
|
|
$selectorElements = $selectors[$i]->elements; |
5802
|
|
|
// duplicate the logic in genCSS function inside the selector node. |
5803
|
|
|
// future TODO - move both logics into the selector joiner visitor |
5804
|
|
|
if( $i && $selectorElements && $selectorElements[0]->combinator === "") { |
5805
|
|
|
$selectorElements[0]->combinator = ' '; |
5806
|
|
|
} |
5807
|
|
|
$selfElements = array_merge( $selfElements, $selectors[$i]->elements ); |
5808
|
|
|
} |
5809
|
|
|
|
5810
|
|
|
$this->selfSelectors = array(new Less_Tree_Selector($selfElements)); |
5811
|
|
|
} |
5812
|
|
|
|
5813
|
|
|
} |
5814
|
|
|
|
5815
|
|
|
/** |
5816
|
|
|
* CSS @import node |
5817
|
|
|
* |
5818
|
|
|
* The general strategy here is that we don't want to wait |
5819
|
|
|
* for the parsing to be completed, before we start importing |
5820
|
|
|
* the file. That's because in the context of a browser, |
5821
|
|
|
* most of the time will be spent waiting for the server to respond. |
5822
|
|
|
* |
5823
|
|
|
* On creation, we push the import path to our import queue, though |
5824
|
|
|
* `import,push`, we also pass it a callback, which it'll call once |
5825
|
|
|
* the file has been fetched, and parsed. |
5826
|
|
|
* |
5827
|
|
|
* @package Less |
5828
|
|
|
* @subpackage tree |
5829
|
|
|
*/ |
5830
|
|
|
class Less_Tree_Import extends Less_Tree{ |
5831
|
|
|
|
5832
|
|
|
public $options; |
5833
|
|
|
public $index; |
5834
|
|
|
public $path; |
5835
|
|
|
public $features; |
5836
|
|
|
public $currentFileInfo; |
5837
|
|
|
public $css; |
5838
|
|
|
public $skip; |
5839
|
|
|
public $root; |
5840
|
|
|
public $type = 'Import'; |
5841
|
|
|
|
5842
|
|
|
public function __construct($path, $features, $options, $index, $currentFileInfo = null ){ |
5843
|
|
|
$this->options = $options; |
5844
|
|
|
$this->index = $index; |
5845
|
|
|
$this->path = $path; |
5846
|
|
|
$this->features = $features; |
5847
|
|
|
$this->currentFileInfo = $currentFileInfo; |
5848
|
|
|
|
5849
|
|
|
if( is_array($options) ){ |
5850
|
|
|
$this->options += array('inline'=>false); |
5851
|
|
|
|
5852
|
|
|
if( isset($this->options['less']) || $this->options['inline'] ){ |
5853
|
|
|
$this->css = !isset($this->options['less']) || !$this->options['less'] || $this->options['inline']; |
5854
|
|
|
} else { |
5855
|
|
|
$pathValue = $this->getPath(); |
5856
|
|
|
if( $pathValue && preg_match('/css([\?;].*)?$/',$pathValue) ){ |
5857
|
|
|
$this->css = true; |
5858
|
|
|
} |
5859
|
|
|
} |
5860
|
|
|
} |
5861
|
|
|
} |
5862
|
|
|
|
5863
|
|
|
// |
5864
|
|
|
// The actual import node doesn't return anything, when converted to CSS. |
5865
|
|
|
// The reason is that it's used at the evaluation stage, so that the rules |
5866
|
|
|
// it imports can be treated like any other rules. |
5867
|
|
|
// |
5868
|
|
|
// In `eval`, we make sure all Import nodes get evaluated, recursively, so |
5869
|
|
|
// we end up with a flat structure, which can easily be imported in the parent |
5870
|
|
|
// ruleset. |
5871
|
|
|
// |
5872
|
|
|
|
5873
|
|
|
public function accept($visitor){ |
5874
|
|
|
|
5875
|
|
|
if( $this->features ){ |
5876
|
|
|
$this->features = $visitor->visitObj($this->features); |
5877
|
|
|
} |
5878
|
|
|
$this->path = $visitor->visitObj($this->path); |
5879
|
|
|
|
5880
|
|
|
if( !$this->options['inline'] && $this->root ){ |
5881
|
|
|
$this->root = $visitor->visit($this->root); |
5882
|
|
|
} |
5883
|
|
|
} |
5884
|
|
|
|
5885
|
|
|
/** |
5886
|
|
|
* @see Less_Tree::genCSS |
5887
|
|
|
*/ |
5888
|
|
|
public function genCSS( $output ){ |
5889
|
|
|
if( $this->css ){ |
5890
|
|
|
|
5891
|
|
|
$output->add( '@import ', $this->currentFileInfo, $this->index ); |
5892
|
|
|
|
5893
|
|
|
$this->path->genCSS( $output ); |
5894
|
|
|
if( $this->features ){ |
5895
|
|
|
$output->add( ' ' ); |
5896
|
|
|
$this->features->genCSS( $output ); |
5897
|
|
|
} |
5898
|
|
|
$output->add( ';' ); |
5899
|
|
|
} |
5900
|
|
|
} |
5901
|
|
|
|
5902
|
|
|
public function toCSS(){ |
5903
|
|
|
$features = $this->features ? ' ' . $this->features->toCSS() : ''; |
5904
|
|
|
|
5905
|
|
|
if ($this->css) { |
5906
|
|
|
return "@import " . $this->path->toCSS() . $features . ";\n"; |
5907
|
|
|
} else { |
5908
|
|
|
return ""; |
5909
|
|
|
} |
5910
|
|
|
} |
5911
|
|
|
|
5912
|
|
|
/** |
5913
|
|
|
* @return string |
5914
|
|
|
*/ |
5915
|
|
|
public function getPath(){ |
5916
|
|
|
if ($this->path instanceof Less_Tree_Quoted) { |
5917
|
|
|
$path = $this->path->value; |
5918
|
|
|
$path = ( isset($this->css) || preg_match('/(\.[a-z]*$)|([\?;].*)$/',$path)) ? $path : $path . '.less'; |
5919
|
|
|
} else if ($this->path instanceof Less_Tree_URL) { |
5920
|
|
|
$path = $this->path->value->value; |
5921
|
|
|
}else{ |
5922
|
|
|
return null; |
5923
|
|
|
} |
5924
|
|
|
|
5925
|
|
|
//remove query string and fragment |
5926
|
|
|
return preg_replace('/[\?#][^\?]*$/','',$path); |
5927
|
|
|
} |
5928
|
|
|
|
5929
|
|
|
public function compileForImport( $env ){ |
5930
|
|
|
return new Less_Tree_Import( $this->path->compile($env), $this->features, $this->options, $this->index, $this->currentFileInfo); |
5931
|
|
|
} |
5932
|
|
|
|
5933
|
|
|
public function compilePath($env) { |
5934
|
|
|
$path = $this->path->compile($env); |
5935
|
|
|
$rootpath = ''; |
5936
|
|
|
if( $this->currentFileInfo && $this->currentFileInfo['rootpath'] ){ |
5937
|
|
|
$rootpath = $this->currentFileInfo['rootpath']; |
5938
|
|
|
} |
5939
|
|
|
|
5940
|
|
|
|
5941
|
|
|
if( !($path instanceof Less_Tree_URL) ){ |
5942
|
|
|
if( $rootpath ){ |
5943
|
|
|
$pathValue = $path->value; |
5944
|
|
|
// Add the base path if the import is relative |
5945
|
|
|
if( $pathValue && Less_Environment::isPathRelative($pathValue) ){ |
5946
|
|
|
$path->value = $this->currentFileInfo['uri_root'].$pathValue; |
5947
|
|
|
} |
5948
|
|
|
} |
5949
|
|
|
$path->value = Less_Environment::normalizePath($path->value); |
5950
|
|
|
} |
5951
|
|
|
|
5952
|
|
|
|
5953
|
|
|
|
5954
|
|
|
return $path; |
5955
|
|
|
} |
5956
|
|
|
|
5957
|
|
|
public function compile( $env ){ |
5958
|
|
|
|
5959
|
|
|
$evald = $this->compileForImport($env); |
5960
|
|
|
|
5961
|
|
|
//get path & uri |
5962
|
|
|
$path_and_uri = null; |
5963
|
|
|
if( is_callable(Less_Parser::$options['import_callback']) ){ |
5964
|
|
|
$path_and_uri = call_user_func(Less_Parser::$options['import_callback'],$evald); |
5965
|
|
|
} |
5966
|
|
|
|
5967
|
|
|
if( !$path_and_uri ){ |
5968
|
|
|
$path_and_uri = $evald->PathAndUri(); |
5969
|
|
|
} |
5970
|
|
|
|
5971
|
|
|
if( $path_and_uri ){ |
5972
|
|
|
list($full_path, $uri) = $path_and_uri; |
5973
|
|
|
}else{ |
5974
|
|
|
$full_path = $uri = $evald->getPath(); |
5975
|
|
|
} |
5976
|
|
|
|
5977
|
|
|
|
5978
|
|
|
//import once |
5979
|
|
|
if( $evald->skip( $full_path, $env) ){ |
5980
|
|
|
return array(); |
5981
|
|
|
} |
5982
|
|
|
|
5983
|
|
|
if( $this->options['inline'] ){ |
5984
|
|
|
//todo needs to reference css file not import |
5985
|
|
|
//$contents = new Less_Tree_Anonymous($this->root, 0, array('filename'=>$this->importedFilename), true ); |
5986
|
|
|
|
5987
|
|
|
Less_Parser::AddParsedFile($full_path); |
5988
|
|
|
$contents = new Less_Tree_Anonymous( file_get_contents($full_path), 0, array(), true ); |
5989
|
|
|
|
5990
|
|
|
if( $this->features ){ |
5991
|
|
|
return new Less_Tree_Media( array($contents), $this->features->value ); |
5992
|
|
|
} |
5993
|
|
|
|
5994
|
|
|
return array( $contents ); |
5995
|
|
|
} |
5996
|
|
|
|
5997
|
|
|
|
5998
|
|
|
// css ? |
5999
|
|
|
if( $evald->css ){ |
6000
|
|
|
$features = ( $evald->features ? $evald->features->compile($env) : null ); |
6001
|
|
|
return new Less_Tree_Import( $this->compilePath( $env), $features, $this->options, $this->index); |
6002
|
|
|
} |
6003
|
|
|
|
6004
|
|
|
|
6005
|
|
|
return $this->ParseImport( $full_path, $uri, $env ); |
6006
|
|
|
} |
6007
|
|
|
|
6008
|
|
|
|
6009
|
|
|
/** |
6010
|
|
|
* Using the import directories, get the full absolute path and uri of the import |
6011
|
|
|
* |
6012
|
|
|
* @param Less_Tree_Import $evald |
6013
|
|
|
*/ |
6014
|
|
|
public function PathAndUri(){ |
6015
|
|
|
|
6016
|
|
|
$evald_path = $this->getPath(); |
6017
|
|
|
|
6018
|
|
|
if( $evald_path ){ |
6019
|
|
|
|
6020
|
|
|
$import_dirs = array(); |
6021
|
|
|
|
6022
|
|
|
if( Less_Environment::isPathRelative($evald_path) ){ |
6023
|
|
|
//if the path is relative, the file should be in the current directory |
6024
|
|
|
$import_dirs[ $this->currentFileInfo['currentDirectory'] ] = $this->currentFileInfo['uri_root']; |
6025
|
|
|
|
6026
|
|
|
}else{ |
6027
|
|
|
//otherwise, the file should be relative to the server root |
6028
|
|
|
$import_dirs[ $this->currentFileInfo['entryPath'] ] = $this->currentFileInfo['entryUri']; |
6029
|
|
|
|
6030
|
|
|
//if the user supplied entryPath isn't the actual root |
6031
|
|
|
$import_dirs[ $_SERVER['DOCUMENT_ROOT'] ] = ''; |
6032
|
|
|
|
6033
|
|
|
} |
6034
|
|
|
|
6035
|
|
|
// always look in user supplied import directories |
6036
|
|
|
$import_dirs = array_merge( $import_dirs, Less_Parser::$options['import_dirs'] ); |
6037
|
|
|
|
6038
|
|
|
|
6039
|
|
|
foreach( $import_dirs as $rootpath => $rooturi){ |
6040
|
|
|
if( is_callable($rooturi) ){ |
6041
|
|
|
list($path, $uri) = call_user_func($rooturi, $evald_path); |
6042
|
|
|
if( is_string($path) ){ |
6043
|
|
|
$full_path = $path; |
6044
|
|
|
return array( $full_path, $uri ); |
6045
|
|
|
} |
6046
|
|
|
}elseif( !empty($rootpath) ){ |
6047
|
|
|
|
6048
|
|
|
|
6049
|
|
|
if( $rooturi ){ |
6050
|
|
|
if( strpos($evald_path,$rooturi) === 0 ){ |
6051
|
|
|
$evald_path = substr( $evald_path, strlen($rooturi) ); |
6052
|
|
|
} |
6053
|
|
|
} |
6054
|
|
|
|
6055
|
|
|
$path = rtrim($rootpath,'/\\').'/'.ltrim($evald_path,'/\\'); |
6056
|
|
|
|
6057
|
|
|
if( file_exists($path) ){ |
6058
|
|
|
$full_path = Less_Environment::normalizePath($path); |
6059
|
|
|
$uri = Less_Environment::normalizePath(dirname($rooturi.$evald_path)); |
6060
|
|
|
return array( $full_path, $uri ); |
6061
|
|
|
} elseif( file_exists($path.'.less') ){ |
6062
|
|
|
$full_path = Less_Environment::normalizePath($path.'.less'); |
6063
|
|
|
$uri = Less_Environment::normalizePath(dirname($rooturi.$evald_path.'.less')); |
6064
|
|
|
return array( $full_path, $uri ); |
6065
|
|
|
} |
6066
|
|
|
} |
6067
|
|
|
} |
6068
|
|
|
} |
6069
|
|
|
} |
6070
|
|
|
|
6071
|
|
|
|
6072
|
|
|
/** |
6073
|
|
|
* Parse the import url and return the rules |
6074
|
|
|
* |
6075
|
|
|
* @return Less_Tree_Media|array |
6076
|
|
|
*/ |
6077
|
|
|
public function ParseImport( $full_path, $uri, $env ){ |
6078
|
|
|
|
6079
|
|
|
$import_env = clone $env; |
6080
|
|
|
if( (isset($this->options['reference']) && $this->options['reference']) || isset($this->currentFileInfo['reference']) ){ |
6081
|
|
|
$import_env->currentFileInfo['reference'] = true; |
6082
|
|
|
} |
6083
|
|
|
|
6084
|
|
|
if( (isset($this->options['multiple']) && $this->options['multiple']) ){ |
6085
|
|
|
$import_env->importMultiple = true; |
6086
|
|
|
} |
6087
|
|
|
|
6088
|
|
|
$parser = new Less_Parser($import_env); |
6089
|
|
|
$root = $parser->parseFile($full_path, $uri, true); |
6090
|
|
|
|
6091
|
|
|
|
6092
|
|
|
$ruleset = new Less_Tree_Ruleset(array(), $root->rules ); |
6093
|
|
|
$ruleset->evalImports($import_env); |
6094
|
|
|
|
6095
|
|
|
return $this->features ? new Less_Tree_Media($ruleset->rules, $this->features->value) : $ruleset->rules; |
6096
|
|
|
} |
6097
|
|
|
|
6098
|
|
|
|
6099
|
|
|
/** |
6100
|
|
|
* Should the import be skipped? |
6101
|
|
|
* |
6102
|
|
|
* @return boolean|null |
6103
|
|
|
*/ |
6104
|
|
|
private function Skip($path, $env){ |
6105
|
|
|
|
6106
|
|
|
$path = Less_Parser::winPath(realpath($path)); |
6107
|
|
|
|
6108
|
|
|
if( $path && Less_Parser::FileParsed($path) ){ |
6109
|
|
|
|
6110
|
|
|
if( isset($this->currentFileInfo['reference']) ){ |
6111
|
|
|
return true; |
6112
|
|
|
} |
6113
|
|
|
|
6114
|
|
|
return !isset($this->options['multiple']) && !$env->importMultiple; |
6115
|
|
|
} |
6116
|
|
|
|
6117
|
|
|
} |
6118
|
|
|
} |
6119
|
|
|
|
6120
|
|
|
|
6121
|
|
|
|
6122
|
|
|
/** |
6123
|
|
|
* Javascript |
6124
|
|
|
* |
6125
|
|
|
* @package Less |
6126
|
|
|
* @subpackage tree |
6127
|
|
|
*/ |
6128
|
|
|
class Less_Tree_Javascript extends Less_Tree{ |
6129
|
|
|
|
6130
|
|
|
public $type = 'Javascript'; |
6131
|
|
|
public $escaped; |
6132
|
|
|
public $expression; |
6133
|
|
|
public $index; |
6134
|
|
|
|
6135
|
|
|
/** |
6136
|
|
|
* @param boolean $index |
6137
|
|
|
* @param boolean $escaped |
6138
|
|
|
*/ |
6139
|
|
|
public function __construct($string, $index, $escaped){ |
6140
|
|
|
$this->escaped = $escaped; |
6141
|
|
|
$this->expression = $string; |
6142
|
|
|
$this->index = $index; |
6143
|
|
|
} |
6144
|
|
|
|
6145
|
|
|
public function compile(){ |
6146
|
|
|
return new Less_Tree_Anonymous('/* Sorry, can not do JavaScript evaluation in PHP... :( */'); |
6147
|
|
|
} |
6148
|
|
|
|
6149
|
|
|
} |
6150
|
|
|
|
6151
|
|
|
|
6152
|
|
|
/** |
6153
|
|
|
* Keyword |
6154
|
|
|
* |
6155
|
|
|
* @package Less |
6156
|
|
|
* @subpackage tree |
6157
|
|
|
*/ |
6158
|
|
|
class Less_Tree_Keyword extends Less_Tree{ |
6159
|
|
|
|
6160
|
|
|
public $value; |
6161
|
|
|
public $type = 'Keyword'; |
6162
|
|
|
|
6163
|
|
|
/** |
6164
|
|
|
* @param string $value |
6165
|
|
|
*/ |
6166
|
|
|
public function __construct($value){ |
6167
|
|
|
$this->value = $value; |
6168
|
|
|
} |
6169
|
|
|
|
6170
|
|
|
public function compile(){ |
6171
|
|
|
return $this; |
6172
|
|
|
} |
6173
|
|
|
|
6174
|
|
|
/** |
6175
|
|
|
* @see Less_Tree::genCSS |
6176
|
|
|
*/ |
6177
|
|
|
public function genCSS( $output ){ |
6178
|
|
|
|
6179
|
|
|
if( $this->value === '%') { |
6180
|
|
|
throw new Less_Exception_Compiler("Invalid % without number"); |
6181
|
|
|
} |
6182
|
|
|
|
6183
|
|
|
$output->add( $this->value ); |
6184
|
|
|
} |
6185
|
|
|
|
6186
|
|
|
public function compare($other) { |
6187
|
|
|
if ($other instanceof Less_Tree_Keyword) { |
6188
|
|
|
return $other->value === $this->value ? 0 : 1; |
6189
|
|
|
} else { |
6190
|
|
|
return -1; |
6191
|
|
|
} |
6192
|
|
|
} |
6193
|
|
|
} |
6194
|
|
|
|
6195
|
|
|
|
6196
|
|
|
/** |
6197
|
|
|
* Media |
6198
|
|
|
* |
6199
|
|
|
* @package Less |
6200
|
|
|
* @subpackage tree |
6201
|
|
|
*/ |
6202
|
|
|
class Less_Tree_Media extends Less_Tree{ |
6203
|
|
|
|
6204
|
|
|
public $features; |
6205
|
|
|
public $rules; |
6206
|
|
|
public $index; |
6207
|
|
|
public $currentFileInfo; |
6208
|
|
|
public $isReferenced; |
6209
|
|
|
public $type = 'Media'; |
6210
|
|
|
|
6211
|
|
|
public function __construct($value = array(), $features = array(), $index = null, $currentFileInfo = null ){ |
6212
|
|
|
|
6213
|
|
|
$this->index = $index; |
6214
|
|
|
$this->currentFileInfo = $currentFileInfo; |
6215
|
|
|
|
6216
|
|
|
$selectors = $this->emptySelectors(); |
6217
|
|
|
|
6218
|
|
|
$this->features = new Less_Tree_Value($features); |
6219
|
|
|
|
6220
|
|
|
$this->rules = array(new Less_Tree_Ruleset($selectors, $value)); |
6221
|
|
|
$this->rules[0]->allowImports = true; |
6222
|
|
|
} |
6223
|
|
|
|
6224
|
|
|
public function accept( $visitor ){ |
6225
|
|
|
$this->features = $visitor->visitObj($this->features); |
6226
|
|
|
$this->rules = $visitor->visitArray($this->rules); |
6227
|
|
|
} |
6228
|
|
|
|
6229
|
|
|
/** |
6230
|
|
|
* @see Less_Tree::genCSS |
6231
|
|
|
*/ |
6232
|
|
|
public function genCSS( $output ){ |
6233
|
|
|
|
6234
|
|
|
$output->add( '@media ', $this->currentFileInfo, $this->index ); |
6235
|
|
|
$this->features->genCSS( $output ); |
6236
|
|
|
Less_Tree::outputRuleset( $output, $this->rules); |
6237
|
|
|
|
6238
|
|
|
} |
6239
|
|
|
|
6240
|
|
|
public function compile($env) { |
6241
|
|
|
|
6242
|
|
|
$media = new Less_Tree_Media(array(), array(), $this->index, $this->currentFileInfo ); |
6243
|
|
|
|
6244
|
|
|
$strictMathBypass = false; |
6245
|
|
|
if( Less_Parser::$options['strictMath'] === false) { |
6246
|
|
|
$strictMathBypass = true; |
6247
|
|
|
Less_Parser::$options['strictMath'] = true; |
6248
|
|
|
} |
6249
|
|
|
|
6250
|
|
|
$media->features = $this->features->compile($env); |
6251
|
|
|
|
6252
|
|
|
if( $strictMathBypass ){ |
6253
|
|
|
Less_Parser::$options['strictMath'] = false; |
6254
|
|
|
} |
6255
|
|
|
|
6256
|
|
|
$env->mediaPath[] = $media; |
6257
|
|
|
$env->mediaBlocks[] = $media; |
6258
|
|
|
|
6259
|
|
|
array_unshift($env->frames, $this->rules[0]); |
6260
|
|
|
$media->rules = array($this->rules[0]->compile($env)); |
6261
|
|
|
array_shift($env->frames); |
6262
|
|
|
|
6263
|
|
|
array_pop($env->mediaPath); |
6264
|
|
|
|
6265
|
|
|
return !$env->mediaPath ? $media->compileTop($env) : $media->compileNested($env); |
6266
|
|
|
} |
6267
|
|
|
|
6268
|
|
|
public function variable($name) { |
6269
|
|
|
return $this->rules[0]->variable($name); |
6270
|
|
|
} |
6271
|
|
|
|
6272
|
|
|
public function find($selector) { |
6273
|
|
|
return $this->rules[0]->find($selector, $this); |
6274
|
|
|
} |
6275
|
|
|
|
6276
|
|
|
public function emptySelectors(){ |
6277
|
|
|
$el = new Less_Tree_Element('','&', $this->index, $this->currentFileInfo ); |
6278
|
|
|
$sels = array( new Less_Tree_Selector(array($el), array(), null, $this->index, $this->currentFileInfo) ); |
6279
|
|
|
$sels[0]->mediaEmpty = true; |
6280
|
|
|
return $sels; |
6281
|
|
|
} |
6282
|
|
|
|
6283
|
|
|
public function markReferenced(){ |
6284
|
|
|
$this->rules[0]->markReferenced(); |
6285
|
|
|
$this->isReferenced = true; |
6286
|
|
|
Less_Tree::ReferencedArray($this->rules[0]->rules); |
6287
|
|
|
} |
6288
|
|
|
|
6289
|
|
|
// evaltop |
6290
|
|
|
public function compileTop($env) { |
6291
|
|
|
$result = $this; |
6292
|
|
|
|
6293
|
|
|
if (count($env->mediaBlocks) > 1) { |
6294
|
|
|
$selectors = $this->emptySelectors(); |
6295
|
|
|
$result = new Less_Tree_Ruleset($selectors, $env->mediaBlocks); |
6296
|
|
|
$result->multiMedia = true; |
6297
|
|
|
} |
6298
|
|
|
|
6299
|
|
|
$env->mediaBlocks = array(); |
6300
|
|
|
$env->mediaPath = array(); |
6301
|
|
|
|
6302
|
|
|
return $result; |
6303
|
|
|
} |
6304
|
|
|
|
6305
|
|
|
public function compileNested($env) { |
6306
|
|
|
$path = array_merge($env->mediaPath, array($this)); |
6307
|
|
|
|
6308
|
|
|
// Extract the media-query conditions separated with `,` (OR). |
6309
|
|
|
foreach ($path as $key => $p) { |
6310
|
|
|
$value = $p->features instanceof Less_Tree_Value ? $p->features->value : $p->features; |
6311
|
|
|
$path[$key] = is_array($value) ? $value : array($value); |
6312
|
|
|
} |
6313
|
|
|
|
6314
|
|
|
// Trace all permutations to generate the resulting media-query. |
6315
|
|
|
// |
6316
|
|
|
// (a, b and c) with nested (d, e) -> |
6317
|
|
|
// a and d |
6318
|
|
|
// a and e |
6319
|
|
|
// b and c and d |
6320
|
|
|
// b and c and e |
6321
|
|
|
|
6322
|
|
|
$permuted = $this->permute($path); |
6323
|
|
|
$expressions = array(); |
6324
|
|
|
foreach($permuted as $path){ |
6325
|
|
|
|
6326
|
|
|
for( $i=0, $len=count($path); $i < $len; $i++){ |
6327
|
|
|
$path[$i] = Less_Parser::is_method($path[$i], 'toCSS') ? $path[$i] : new Less_Tree_Anonymous($path[$i]); |
6328
|
|
|
} |
6329
|
|
|
|
6330
|
|
|
for( $i = count($path) - 1; $i > 0; $i-- ){ |
6331
|
|
|
array_splice($path, $i, 0, array(new Less_Tree_Anonymous('and'))); |
6332
|
|
|
} |
6333
|
|
|
|
6334
|
|
|
$expressions[] = new Less_Tree_Expression($path); |
6335
|
|
|
} |
6336
|
|
|
$this->features = new Less_Tree_Value($expressions); |
6337
|
|
|
|
6338
|
|
|
|
6339
|
|
|
|
6340
|
|
|
// Fake a tree-node that doesn't output anything. |
6341
|
|
|
return new Less_Tree_Ruleset(array(), array()); |
6342
|
|
|
} |
6343
|
|
|
|
6344
|
|
|
public function permute($arr) { |
6345
|
|
|
if (!$arr) |
6346
|
|
|
return array(); |
6347
|
|
|
|
6348
|
|
|
if (count($arr) == 1) |
6349
|
|
|
return $arr[0]; |
6350
|
|
|
|
6351
|
|
|
$result = array(); |
6352
|
|
|
$rest = $this->permute(array_slice($arr, 1)); |
6353
|
|
|
foreach ($rest as $r) { |
6354
|
|
|
foreach ($arr[0] as $a) { |
6355
|
|
|
$result[] = array_merge( |
6356
|
|
|
is_array($a) ? $a : array($a), |
6357
|
|
|
is_array($r) ? $r : array($r) |
6358
|
|
|
); |
6359
|
|
|
} |
6360
|
|
|
} |
6361
|
|
|
|
6362
|
|
|
return $result; |
6363
|
|
|
} |
6364
|
|
|
|
6365
|
|
|
public function bubbleSelectors($selectors) { |
6366
|
|
|
|
6367
|
|
|
if( !$selectors) return; |
6368
|
|
|
|
6369
|
|
|
$this->rules = array(new Less_Tree_Ruleset( $selectors, array($this->rules[0]))); |
6370
|
|
|
} |
6371
|
|
|
|
6372
|
|
|
} |
6373
|
|
|
|
6374
|
|
|
|
6375
|
|
|
/** |
6376
|
|
|
* A simple css name-value pair |
6377
|
|
|
* ex: width:100px; |
6378
|
|
|
* |
6379
|
|
|
* In bootstrap, there are about 600-1,000 simple name-value pairs (depending on how forgiving the match is) -vs- 6,020 dynamic rules (Less_Tree_Rule) |
6380
|
|
|
* Using the name-value object can speed up bootstrap compilation slightly, but it breaks color keyword interpretation: color:red -> color:#FF0000; |
6381
|
|
|
* |
6382
|
|
|
* @package Less |
6383
|
|
|
* @subpackage tree |
6384
|
|
|
*/ |
6385
|
|
|
class Less_Tree_NameValue extends Less_Tree{ |
6386
|
|
|
|
6387
|
|
|
public $name; |
6388
|
|
|
public $value; |
6389
|
|
|
public $index; |
6390
|
|
|
public $currentFileInfo; |
6391
|
|
|
public $type = 'NameValue'; |
6392
|
|
|
|
6393
|
|
View Code Duplication |
public function __construct($name, $value = null, $index = null, $currentFileInfo = null ){ |
6394
|
|
|
$this->name = $name; |
6395
|
|
|
$this->value = $value; |
6396
|
|
|
$this->index = $index; |
6397
|
|
|
$this->currentFileInfo = $currentFileInfo; |
6398
|
|
|
} |
6399
|
|
|
|
6400
|
|
|
public function genCSS( $output ){ |
6401
|
|
|
|
6402
|
|
|
$output->add( |
6403
|
|
|
$this->name |
6404
|
|
|
. Less_Environment::$_outputMap[': '] |
6405
|
|
|
. $this->value |
6406
|
|
|
. (((Less_Environment::$lastRule && Less_Parser::$options['compress'])) ? "" : ";") |
6407
|
|
|
, $this->currentFileInfo, $this->index); |
6408
|
|
|
} |
6409
|
|
|
|
6410
|
|
|
public function compile ($env){ |
6411
|
|
|
return $this; |
6412
|
|
|
} |
6413
|
|
|
} |
6414
|
|
|
|
6415
|
|
|
|
6416
|
|
|
/** |
6417
|
|
|
* Negative |
6418
|
|
|
* |
6419
|
|
|
* @package Less |
6420
|
|
|
* @subpackage tree |
6421
|
|
|
*/ |
6422
|
|
|
class Less_Tree_Negative extends Less_Tree{ |
6423
|
|
|
|
6424
|
|
|
public $value; |
6425
|
|
|
public $type = 'Negative'; |
6426
|
|
|
|
6427
|
|
|
public function __construct($node){ |
6428
|
|
|
$this->value = $node; |
6429
|
|
|
} |
6430
|
|
|
|
6431
|
|
|
//function accept($visitor) { |
6432
|
|
|
// $this->value = $visitor->visit($this->value); |
6433
|
|
|
//} |
6434
|
|
|
|
6435
|
|
|
/** |
6436
|
|
|
* @see Less_Tree::genCSS |
6437
|
|
|
*/ |
6438
|
|
|
public function genCSS( $output ){ |
6439
|
|
|
$output->add( '-' ); |
6440
|
|
|
$this->value->genCSS( $output ); |
6441
|
|
|
} |
6442
|
|
|
|
6443
|
|
|
public function compile($env) { |
6444
|
|
|
if( Less_Environment::isMathOn() ){ |
6445
|
|
|
$ret = new Less_Tree_Operation('*', array( new Less_Tree_Dimension(-1), $this->value ) ); |
6446
|
|
|
return $ret->compile($env); |
6447
|
|
|
} |
6448
|
|
|
return new Less_Tree_Negative( $this->value->compile($env) ); |
6449
|
|
|
} |
6450
|
|
|
} |
6451
|
|
|
|
6452
|
|
|
/** |
6453
|
|
|
* Operation |
6454
|
|
|
* |
6455
|
|
|
* @package Less |
6456
|
|
|
* @subpackage tree |
6457
|
|
|
*/ |
6458
|
|
|
class Less_Tree_Operation extends Less_Tree{ |
6459
|
|
|
|
6460
|
|
|
public $op; |
6461
|
|
|
public $operands; |
6462
|
|
|
public $isSpaced; |
6463
|
|
|
public $type = 'Operation'; |
6464
|
|
|
|
6465
|
|
|
/** |
6466
|
|
|
* @param string $op |
6467
|
|
|
*/ |
6468
|
|
|
public function __construct($op, $operands, $isSpaced = false){ |
6469
|
|
|
$this->op = trim($op); |
6470
|
|
|
$this->operands = $operands; |
6471
|
|
|
$this->isSpaced = $isSpaced; |
6472
|
|
|
} |
6473
|
|
|
|
6474
|
|
|
public function accept($visitor) { |
6475
|
|
|
$this->operands = $visitor->visitArray($this->operands); |
6476
|
|
|
} |
6477
|
|
|
|
6478
|
|
|
public function compile($env){ |
6479
|
|
|
$a = $this->operands[0]->compile($env); |
6480
|
|
|
$b = $this->operands[1]->compile($env); |
6481
|
|
|
|
6482
|
|
|
|
6483
|
|
|
if( Less_Environment::isMathOn() ){ |
6484
|
|
|
|
6485
|
|
|
if( $a instanceof Less_Tree_Dimension && $b instanceof Less_Tree_Color ){ |
6486
|
|
|
$a = $a->toColor(); |
6487
|
|
|
|
6488
|
|
|
}elseif( $b instanceof Less_Tree_Dimension && $a instanceof Less_Tree_Color ){ |
6489
|
|
|
$b = $b->toColor(); |
6490
|
|
|
|
6491
|
|
|
} |
6492
|
|
|
|
6493
|
|
|
if( !method_exists($a,'operate') ){ |
6494
|
|
|
throw new Less_Exception_Compiler("Operation on an invalid type"); |
6495
|
|
|
} |
6496
|
|
|
|
6497
|
|
|
return $a->operate( $this->op, $b); |
6498
|
|
|
} |
6499
|
|
|
|
6500
|
|
|
return new Less_Tree_Operation($this->op, array($a, $b), $this->isSpaced ); |
6501
|
|
|
} |
6502
|
|
|
|
6503
|
|
|
|
6504
|
|
|
/** |
6505
|
|
|
* @see Less_Tree::genCSS |
6506
|
|
|
*/ |
6507
|
|
|
public function genCSS( $output ){ |
6508
|
|
|
$this->operands[0]->genCSS( $output ); |
6509
|
|
|
if( $this->isSpaced ){ |
6510
|
|
|
$output->add( " " ); |
6511
|
|
|
} |
6512
|
|
|
$output->add( $this->op ); |
6513
|
|
|
if( $this->isSpaced ){ |
6514
|
|
|
$output->add( ' ' ); |
6515
|
|
|
} |
6516
|
|
|
$this->operands[1]->genCSS( $output ); |
6517
|
|
|
} |
6518
|
|
|
|
6519
|
|
|
} |
6520
|
|
|
|
6521
|
|
|
|
6522
|
|
|
/** |
6523
|
|
|
* Paren |
6524
|
|
|
* |
6525
|
|
|
* @package Less |
6526
|
|
|
* @subpackage tree |
6527
|
|
|
*/ |
6528
|
|
|
class Less_Tree_Paren extends Less_Tree{ |
6529
|
|
|
|
6530
|
|
|
public $value; |
6531
|
|
|
public $type = 'Paren'; |
6532
|
|
|
|
6533
|
|
|
public function __construct($value) { |
6534
|
|
|
$this->value = $value; |
6535
|
|
|
} |
6536
|
|
|
|
6537
|
|
|
public function accept($visitor){ |
6538
|
|
|
$this->value = $visitor->visitObj($this->value); |
6539
|
|
|
} |
6540
|
|
|
|
6541
|
|
|
/** |
6542
|
|
|
* @see Less_Tree::genCSS |
6543
|
|
|
*/ |
6544
|
|
|
public function genCSS( $output ){ |
6545
|
|
|
$output->add( '(' ); |
6546
|
|
|
$this->value->genCSS( $output ); |
6547
|
|
|
$output->add( ')' ); |
6548
|
|
|
} |
6549
|
|
|
|
6550
|
|
|
public function compile($env) { |
6551
|
|
|
return new Less_Tree_Paren($this->value->compile($env)); |
6552
|
|
|
} |
6553
|
|
|
|
6554
|
|
|
} |
6555
|
|
|
|
6556
|
|
|
|
6557
|
|
|
/** |
6558
|
|
|
* Quoted |
6559
|
|
|
* |
6560
|
|
|
* @package Less |
6561
|
|
|
* @subpackage tree |
6562
|
|
|
*/ |
6563
|
|
|
class Less_Tree_Quoted extends Less_Tree{ |
6564
|
|
|
public $escaped; |
6565
|
|
|
public $value; |
6566
|
|
|
public $quote; |
6567
|
|
|
public $index; |
6568
|
|
|
public $currentFileInfo; |
6569
|
|
|
public $type = 'Quoted'; |
6570
|
|
|
|
6571
|
|
|
/** |
6572
|
|
|
* @param string $str |
6573
|
|
|
*/ |
6574
|
|
|
public function __construct($str, $content = '', $escaped = false, $index = false, $currentFileInfo = null ){ |
6575
|
|
|
$this->escaped = $escaped; |
6576
|
|
|
$this->value = $content; |
6577
|
|
|
if( $str ){ |
6578
|
|
|
$this->quote = $str[0]; |
6579
|
|
|
} |
6580
|
|
|
$this->index = $index; |
6581
|
|
|
$this->currentFileInfo = $currentFileInfo; |
6582
|
|
|
} |
6583
|
|
|
|
6584
|
|
|
/** |
6585
|
|
|
* @see Less_Tree::genCSS |
6586
|
|
|
*/ |
6587
|
|
|
public function genCSS( $output ){ |
6588
|
|
|
if( !$this->escaped ){ |
6589
|
|
|
$output->add( $this->quote, $this->currentFileInfo, $this->index ); |
6590
|
|
|
} |
6591
|
|
|
$output->add( $this->value ); |
6592
|
|
|
if( !$this->escaped ){ |
6593
|
|
|
$output->add( $this->quote ); |
6594
|
|
|
} |
6595
|
|
|
} |
6596
|
|
|
|
6597
|
|
|
public function compile($env){ |
6598
|
|
|
|
6599
|
|
|
$value = $this->value; |
6600
|
|
|
if( preg_match_all('/`([^`]+)`/', $this->value, $matches) ){ |
6601
|
|
|
foreach($matches as $i => $match){ |
6602
|
|
|
$js = new Less_Tree_JavaScript($matches[1], $this->index, true); |
6603
|
|
|
$js = $js->compile()->value; |
6604
|
|
|
$value = str_replace($matches[0][$i], $js, $value); |
6605
|
|
|
} |
6606
|
|
|
} |
6607
|
|
|
|
6608
|
|
|
if( preg_match_all('/@\{([\w-]+)\}/',$value,$matches) ){ |
6609
|
|
|
foreach($matches[1] as $i => $match){ |
6610
|
|
|
$v = new Less_Tree_Variable('@' . $match, $this->index, $this->currentFileInfo ); |
6611
|
|
|
$v = $v->compile($env); |
6612
|
|
|
$v = ($v instanceof Less_Tree_Quoted) ? $v->value : $v->toCSS(); |
6613
|
|
|
$value = str_replace($matches[0][$i], $v, $value); |
6614
|
|
|
} |
6615
|
|
|
} |
6616
|
|
|
|
6617
|
|
|
return new Less_Tree_Quoted($this->quote . $value . $this->quote, $value, $this->escaped, $this->index, $this->currentFileInfo); |
6618
|
|
|
} |
6619
|
|
|
|
6620
|
|
View Code Duplication |
public function compare($x) { |
6621
|
|
|
|
6622
|
|
|
if( !Less_Parser::is_method($x, 'toCSS') ){ |
6623
|
|
|
return -1; |
6624
|
|
|
} |
6625
|
|
|
|
6626
|
|
|
$left = $this->toCSS(); |
6627
|
|
|
$right = $x->toCSS(); |
6628
|
|
|
|
6629
|
|
|
if ($left === $right) { |
6630
|
|
|
return 0; |
6631
|
|
|
} |
6632
|
|
|
|
6633
|
|
|
return $left < $right ? -1 : 1; |
6634
|
|
|
} |
6635
|
|
|
} |
6636
|
|
|
|
6637
|
|
|
|
6638
|
|
|
/** |
6639
|
|
|
* Rule |
6640
|
|
|
* |
6641
|
|
|
* @package Less |
6642
|
|
|
* @subpackage tree |
6643
|
|
|
*/ |
6644
|
|
|
class Less_Tree_Rule extends Less_Tree{ |
6645
|
|
|
|
6646
|
|
|
public $name; |
6647
|
|
|
public $value; |
6648
|
|
|
public $important; |
6649
|
|
|
public $merge; |
6650
|
|
|
public $index; |
6651
|
|
|
public $inline; |
6652
|
|
|
public $variable; |
6653
|
|
|
public $currentFileInfo; |
6654
|
|
|
public $type = 'Rule'; |
6655
|
|
|
|
6656
|
|
|
/** |
6657
|
|
|
* @param string $important |
6658
|
|
|
*/ |
6659
|
|
|
public function __construct($name, $value = null, $important = null, $merge = null, $index = null, $currentFileInfo = null, $inline = false){ |
6660
|
|
|
$this->name = $name; |
6661
|
|
|
$this->value = ($value instanceof Less_Tree_Value || $value instanceof Less_Tree_Ruleset) ? $value : new Less_Tree_Value(array($value)); |
6662
|
|
|
$this->important = $important ? ' ' . trim($important) : ''; |
6663
|
|
|
$this->merge = $merge; |
6664
|
|
|
$this->index = $index; |
6665
|
|
|
$this->currentFileInfo = $currentFileInfo; |
6666
|
|
|
$this->inline = $inline; |
6667
|
|
|
$this->variable = ( is_string($name) && $name[0] === '@'); |
6668
|
|
|
} |
6669
|
|
|
|
6670
|
|
|
public function accept($visitor) { |
6671
|
|
|
$this->value = $visitor->visitObj( $this->value ); |
6672
|
|
|
} |
6673
|
|
|
|
6674
|
|
|
/** |
6675
|
|
|
* @see Less_Tree::genCSS |
6676
|
|
|
*/ |
6677
|
|
|
public function genCSS( $output ){ |
6678
|
|
|
|
6679
|
|
|
$output->add( $this->name . Less_Environment::$_outputMap[': '], $this->currentFileInfo, $this->index); |
6680
|
|
|
try{ |
6681
|
|
|
$this->value->genCSS( $output); |
6682
|
|
|
|
6683
|
|
|
}catch( Less_Exception_Parser $e ){ |
6684
|
|
|
$e->index = $this->index; |
6685
|
|
|
$e->currentFile = $this->currentFileInfo; |
6686
|
|
|
throw $e; |
6687
|
|
|
} |
6688
|
|
|
$output->add( $this->important . (($this->inline || (Less_Environment::$lastRule && Less_Parser::$options['compress'])) ? "" : ";"), $this->currentFileInfo, $this->index); |
6689
|
|
|
} |
6690
|
|
|
|
6691
|
|
|
public function compile ($env){ |
6692
|
|
|
|
6693
|
|
|
$name = $this->name; |
6694
|
|
|
if( is_array($name) ){ |
6695
|
|
|
// expand 'primitive' name directly to get |
6696
|
|
|
// things faster (~10% for benchmark.less): |
6697
|
|
|
if( count($name) === 1 && $name[0] instanceof Less_Tree_Keyword ){ |
6698
|
|
|
$name = $name[0]->value; |
6699
|
|
|
}else{ |
6700
|
|
|
$name = $this->CompileName($env,$name); |
6701
|
|
|
} |
6702
|
|
|
} |
6703
|
|
|
|
6704
|
|
|
$strictMathBypass = Less_Parser::$options['strictMath']; |
6705
|
|
|
if( $name === "font" && !Less_Parser::$options['strictMath'] ){ |
6706
|
|
|
Less_Parser::$options['strictMath'] = true; |
6707
|
|
|
} |
6708
|
|
|
|
6709
|
|
|
try { |
6710
|
|
|
$evaldValue = $this->value->compile($env); |
6711
|
|
|
|
6712
|
|
|
if( !$this->variable && $evaldValue->type === "DetachedRuleset") { |
6713
|
|
|
throw new Less_Exception_Compiler("Rulesets cannot be evaluated on a property.", null, $this->index, $this->currentFileInfo); |
6714
|
|
|
} |
6715
|
|
|
|
6716
|
|
|
if( Less_Environment::$mixin_stack ){ |
6717
|
|
|
$return = new Less_Tree_Rule($name, $evaldValue, $this->important, $this->merge, $this->index, $this->currentFileInfo, $this->inline); |
6718
|
|
|
}else{ |
6719
|
|
|
$this->name = $name; |
6720
|
|
|
$this->value = $evaldValue; |
6721
|
|
|
$return = $this; |
6722
|
|
|
} |
6723
|
|
|
|
6724
|
|
|
}catch( Less_Exception_Parser $e ){ |
6725
|
|
|
if( !is_numeric($e->index) ){ |
6726
|
|
|
$e->index = $this->index; |
6727
|
|
|
$e->currentFile = $this->currentFileInfo; |
6728
|
|
|
} |
6729
|
|
|
throw $e; |
6730
|
|
|
} |
6731
|
|
|
|
6732
|
|
|
Less_Parser::$options['strictMath'] = $strictMathBypass; |
6733
|
|
|
|
6734
|
|
|
return $return; |
6735
|
|
|
} |
6736
|
|
|
|
6737
|
|
|
|
6738
|
|
|
public function CompileName( $env, $name ){ |
6739
|
|
|
$output = new Less_Output(); |
6740
|
|
|
foreach($name as $n){ |
6741
|
|
|
$n->compile($env)->genCSS($output); |
6742
|
|
|
} |
6743
|
|
|
return $output->toString(); |
6744
|
|
|
} |
6745
|
|
|
|
6746
|
|
|
public function makeImportant(){ |
6747
|
|
|
return new Less_Tree_Rule($this->name, $this->value, '!important', $this->merge, $this->index, $this->currentFileInfo, $this->inline); |
6748
|
|
|
} |
6749
|
|
|
|
6750
|
|
|
} |
6751
|
|
|
|
6752
|
|
|
|
6753
|
|
|
/** |
6754
|
|
|
* Ruleset |
6755
|
|
|
* |
6756
|
|
|
* @package Less |
6757
|
|
|
* @subpackage tree |
6758
|
|
|
*/ |
6759
|
|
|
class Less_Tree_Ruleset extends Less_Tree{ |
6760
|
|
|
|
6761
|
|
|
protected $lookups; |
6762
|
|
|
public $_variables; |
6763
|
|
|
public $_rulesets; |
6764
|
|
|
|
6765
|
|
|
public $strictImports; |
6766
|
|
|
|
6767
|
|
|
public $selectors; |
6768
|
|
|
public $rules; |
6769
|
|
|
public $root; |
6770
|
|
|
public $allowImports; |
6771
|
|
|
public $paths; |
6772
|
|
|
public $firstRoot; |
6773
|
|
|
public $type = 'Ruleset'; |
6774
|
|
|
public $multiMedia; |
6775
|
|
|
public $allExtends; |
6776
|
|
|
|
6777
|
|
|
public $ruleset_id; |
6778
|
|
|
public $originalRuleset; |
6779
|
|
|
|
6780
|
|
|
public $first_oelements; |
6781
|
|
|
|
6782
|
|
|
public function SetRulesetIndex(){ |
6783
|
|
|
$this->ruleset_id = Less_Parser::$next_id++; |
6784
|
|
|
$this->originalRuleset = $this->ruleset_id; |
6785
|
|
|
|
6786
|
|
|
if( $this->selectors ){ |
6787
|
|
|
foreach($this->selectors as $sel){ |
6788
|
|
|
if( $sel->_oelements ){ |
6789
|
|
|
$this->first_oelements[$sel->_oelements[0]] = true; |
6790
|
|
|
} |
6791
|
|
|
} |
6792
|
|
|
} |
6793
|
|
|
} |
6794
|
|
|
|
6795
|
|
|
public function __construct($selectors, $rules, $strictImports = null){ |
6796
|
|
|
$this->selectors = $selectors; |
6797
|
|
|
$this->rules = $rules; |
6798
|
|
|
$this->lookups = array(); |
6799
|
|
|
$this->strictImports = $strictImports; |
6800
|
|
|
$this->SetRulesetIndex(); |
6801
|
|
|
} |
6802
|
|
|
|
6803
|
|
|
public function accept( $visitor ){ |
6804
|
|
|
if( $this->paths ){ |
6805
|
|
|
$paths_len = count($this->paths); |
6806
|
|
|
for($i = 0,$paths_len; $i < $paths_len; $i++ ){ |
6807
|
|
|
$this->paths[$i] = $visitor->visitArray($this->paths[$i]); |
6808
|
|
|
} |
6809
|
|
|
}elseif( $this->selectors ){ |
6810
|
|
|
$this->selectors = $visitor->visitArray($this->selectors); |
6811
|
|
|
} |
6812
|
|
|
|
6813
|
|
|
if( $this->rules ){ |
6814
|
|
|
$this->rules = $visitor->visitArray($this->rules); |
6815
|
|
|
} |
6816
|
|
|
} |
6817
|
|
|
|
6818
|
|
|
public function compile($env){ |
6819
|
|
|
|
6820
|
|
|
$ruleset = $this->PrepareRuleset($env); |
6821
|
|
|
|
6822
|
|
|
|
6823
|
|
|
// Store the frames around mixin definitions, |
6824
|
|
|
// so they can be evaluated like closures when the time comes. |
6825
|
|
|
$rsRuleCnt = count($ruleset->rules); |
6826
|
|
View Code Duplication |
for( $i = 0; $i < $rsRuleCnt; $i++ ){ |
6827
|
|
|
if( $ruleset->rules[$i] instanceof Less_Tree_Mixin_Definition || $ruleset->rules[$i] instanceof Less_Tree_DetachedRuleset ){ |
6828
|
|
|
$ruleset->rules[$i] = $ruleset->rules[$i]->compile($env); |
6829
|
|
|
} |
6830
|
|
|
} |
6831
|
|
|
|
6832
|
|
|
$mediaBlockCount = 0; |
6833
|
|
|
if( $env instanceof Less_Environment ){ |
6834
|
|
|
$mediaBlockCount = count($env->mediaBlocks); |
6835
|
|
|
} |
6836
|
|
|
|
6837
|
|
|
// Evaluate mixin calls. |
6838
|
|
|
$this->EvalMixinCalls( $ruleset, $env, $rsRuleCnt ); |
6839
|
|
|
|
6840
|
|
|
|
6841
|
|
|
// Evaluate everything else |
6842
|
|
View Code Duplication |
for( $i=0; $i<$rsRuleCnt; $i++ ){ |
6843
|
|
|
if(! ($ruleset->rules[$i] instanceof Less_Tree_Mixin_Definition || $ruleset->rules[$i] instanceof Less_Tree_DetachedRuleset) ){ |
6844
|
|
|
$ruleset->rules[$i] = $ruleset->rules[$i]->compile($env); |
6845
|
|
|
} |
6846
|
|
|
} |
6847
|
|
|
|
6848
|
|
|
// Evaluate everything else |
6849
|
|
|
for( $i=0; $i<$rsRuleCnt; $i++ ){ |
6850
|
|
|
$rule = $ruleset->rules[$i]; |
6851
|
|
|
|
6852
|
|
|
// for rulesets, check if it is a css guard and can be removed |
6853
|
|
|
if( $rule instanceof Less_Tree_Ruleset && $rule->selectors && count($rule->selectors) === 1 ){ |
6854
|
|
|
|
6855
|
|
|
// check if it can be folded in (e.g. & where) |
6856
|
|
|
if( $rule->selectors[0]->isJustParentSelector() ){ |
6857
|
|
|
array_splice($ruleset->rules,$i--,1); |
6858
|
|
|
$rsRuleCnt--; |
6859
|
|
|
|
6860
|
|
|
for($j = 0; $j < count($rule->rules); $j++ ){ |
|
|
|
|
6861
|
|
|
$subRule = $rule->rules[$j]; |
6862
|
|
|
if( !($subRule instanceof Less_Tree_Rule) || !$subRule->variable ){ |
6863
|
|
|
array_splice($ruleset->rules, ++$i, 0, array($subRule)); |
6864
|
|
|
$rsRuleCnt++; |
6865
|
|
|
} |
6866
|
|
|
} |
6867
|
|
|
|
6868
|
|
|
} |
6869
|
|
|
} |
6870
|
|
|
} |
6871
|
|
|
|
6872
|
|
|
|
6873
|
|
|
// Pop the stack |
6874
|
|
|
$env->shiftFrame(); |
6875
|
|
|
|
6876
|
|
|
if ($mediaBlockCount) { |
6877
|
|
|
$len = count($env->mediaBlocks); |
6878
|
|
|
for($i = $mediaBlockCount; $i < $len; $i++ ){ |
6879
|
|
|
$env->mediaBlocks[$i]->bubbleSelectors($ruleset->selectors); |
6880
|
|
|
} |
6881
|
|
|
} |
6882
|
|
|
|
6883
|
|
|
return $ruleset; |
6884
|
|
|
} |
6885
|
|
|
|
6886
|
|
|
/** |
6887
|
|
|
* Compile Less_Tree_Mixin_Call objects |
6888
|
|
|
* |
6889
|
|
|
* @param Less_Tree_Ruleset $ruleset |
6890
|
|
|
* @param integer $rsRuleCnt |
6891
|
|
|
*/ |
6892
|
|
|
private function EvalMixinCalls( $ruleset, $env, &$rsRuleCnt ){ |
6893
|
|
|
for($i=0; $i < $rsRuleCnt; $i++){ |
6894
|
|
|
$rule = $ruleset->rules[$i]; |
6895
|
|
|
|
6896
|
|
|
if( $rule instanceof Less_Tree_Mixin_Call ){ |
6897
|
|
|
$rule = $rule->compile($env); |
6898
|
|
|
|
6899
|
|
|
$temp = array(); |
6900
|
|
|
foreach($rule as $r){ |
6901
|
|
|
if( ($r instanceof Less_Tree_Rule) && $r->variable ){ |
6902
|
|
|
// do not pollute the scope if the variable is |
6903
|
|
|
// already there. consider returning false here |
6904
|
|
|
// but we need a way to "return" variable from mixins |
6905
|
|
|
if( !$ruleset->variable($r->name) ){ |
6906
|
|
|
$temp[] = $r; |
6907
|
|
|
} |
6908
|
|
|
}else{ |
6909
|
|
|
$temp[] = $r; |
6910
|
|
|
} |
6911
|
|
|
} |
6912
|
|
|
$temp_count = count($temp)-1; |
6913
|
|
|
array_splice($ruleset->rules, $i, 1, $temp); |
6914
|
|
|
$rsRuleCnt += $temp_count; |
6915
|
|
|
$i += $temp_count; |
6916
|
|
|
$ruleset->resetCache(); |
6917
|
|
|
|
6918
|
|
|
}elseif( $rule instanceof Less_Tree_RulesetCall ){ |
6919
|
|
|
|
6920
|
|
|
$rule = $rule->compile($env); |
6921
|
|
|
$rules = array(); |
6922
|
|
|
foreach($rule->rules as $r){ |
6923
|
|
|
if( ($r instanceof Less_Tree_Rule) && $r->variable ){ |
6924
|
|
|
continue; |
6925
|
|
|
} |
6926
|
|
|
$rules[] = $r; |
6927
|
|
|
} |
6928
|
|
|
|
6929
|
|
|
array_splice($ruleset->rules, $i, 1, $rules); |
6930
|
|
|
$temp_count = count($rules); |
6931
|
|
|
$rsRuleCnt += $temp_count - 1; |
6932
|
|
|
$i += $temp_count-1; |
6933
|
|
|
$ruleset->resetCache(); |
6934
|
|
|
} |
6935
|
|
|
|
6936
|
|
|
} |
6937
|
|
|
} |
6938
|
|
|
|
6939
|
|
|
|
6940
|
|
|
/** |
6941
|
|
|
* Compile the selectors and create a new ruleset object for the compile() method |
6942
|
|
|
* |
6943
|
|
|
*/ |
6944
|
|
|
private function PrepareRuleset($env){ |
6945
|
|
|
|
6946
|
|
|
$hasOnePassingSelector = false; |
6947
|
|
|
$selectors = array(); |
6948
|
|
|
if( $this->selectors ){ |
6949
|
|
|
Less_Tree_DefaultFunc::error("it is currently only allowed in parametric mixin guards,"); |
6950
|
|
|
|
6951
|
|
|
foreach($this->selectors as $s){ |
6952
|
|
|
$selector = $s->compile($env); |
6953
|
|
|
$selectors[] = $selector; |
6954
|
|
|
if( $selector->evaldCondition ){ |
6955
|
|
|
$hasOnePassingSelector = true; |
6956
|
|
|
} |
6957
|
|
|
} |
6958
|
|
|
|
6959
|
|
|
Less_Tree_DefaultFunc::reset(); |
6960
|
|
|
} else { |
6961
|
|
|
$hasOnePassingSelector = true; |
6962
|
|
|
} |
6963
|
|
|
|
6964
|
|
|
if( $this->rules && $hasOnePassingSelector ){ |
6965
|
|
|
$rules = $this->rules; |
6966
|
|
|
}else{ |
6967
|
|
|
$rules = array(); |
6968
|
|
|
} |
6969
|
|
|
|
6970
|
|
|
$ruleset = new Less_Tree_Ruleset($selectors, $rules, $this->strictImports); |
6971
|
|
|
|
6972
|
|
|
$ruleset->originalRuleset = $this->ruleset_id; |
6973
|
|
|
|
6974
|
|
|
$ruleset->root = $this->root; |
6975
|
|
|
$ruleset->firstRoot = $this->firstRoot; |
6976
|
|
|
$ruleset->allowImports = $this->allowImports; |
6977
|
|
|
|
6978
|
|
|
|
6979
|
|
|
// push the current ruleset to the frames stack |
6980
|
|
|
$env->unshiftFrame($ruleset); |
6981
|
|
|
|
6982
|
|
|
|
6983
|
|
|
// Evaluate imports |
6984
|
|
|
if( $ruleset->root || $ruleset->allowImports || !$ruleset->strictImports ){ |
6985
|
|
|
$ruleset->evalImports($env); |
6986
|
|
|
} |
6987
|
|
|
|
6988
|
|
|
return $ruleset; |
6989
|
|
|
} |
6990
|
|
|
|
6991
|
|
|
function evalImports($env) { |
|
|
|
|
6992
|
|
|
|
6993
|
|
|
$rules_len = count($this->rules); |
6994
|
|
|
for($i=0; $i < $rules_len; $i++){ |
6995
|
|
|
$rule = $this->rules[$i]; |
6996
|
|
|
|
6997
|
|
|
if( $rule instanceof Less_Tree_Import ){ |
6998
|
|
|
$rules = $rule->compile($env); |
6999
|
|
|
if( is_array($rules) ){ |
7000
|
|
|
array_splice($this->rules, $i, 1, $rules); |
7001
|
|
|
$temp_count = count($rules)-1; |
7002
|
|
|
$i += $temp_count; |
7003
|
|
|
$rules_len += $temp_count; |
7004
|
|
|
}else{ |
7005
|
|
|
array_splice($this->rules, $i, 1, array($rules)); |
7006
|
|
|
} |
7007
|
|
|
|
7008
|
|
|
$this->resetCache(); |
7009
|
|
|
} |
7010
|
|
|
} |
7011
|
|
|
} |
7012
|
|
|
|
7013
|
|
|
function makeImportant(){ |
|
|
|
|
7014
|
|
|
|
7015
|
|
|
$important_rules = array(); |
7016
|
|
|
foreach($this->rules as $rule){ |
7017
|
|
|
if( $rule instanceof Less_Tree_Rule || $rule instanceof Less_Tree_Ruleset ){ |
7018
|
|
|
$important_rules[] = $rule->makeImportant(); |
7019
|
|
|
}else{ |
7020
|
|
|
$important_rules[] = $rule; |
7021
|
|
|
} |
7022
|
|
|
} |
7023
|
|
|
|
7024
|
|
|
return new Less_Tree_Ruleset($this->selectors, $important_rules, $this->strictImports ); |
7025
|
|
|
} |
7026
|
|
|
|
7027
|
|
|
public function matchArgs($args){ |
7028
|
|
|
return !$args; |
7029
|
|
|
} |
7030
|
|
|
|
7031
|
|
|
// lets you call a css selector with a guard |
7032
|
|
|
public function matchCondition( $args, $env ){ |
7033
|
|
|
$lastSelector = end($this->selectors); |
7034
|
|
|
|
7035
|
|
|
if( !$lastSelector->evaldCondition ){ |
7036
|
|
|
return false; |
7037
|
|
|
} |
7038
|
|
|
if( $lastSelector->condition && !$lastSelector->condition->compile( $env->copyEvalEnv( $env->frames ) ) ){ |
7039
|
|
|
return false; |
7040
|
|
|
} |
7041
|
|
|
return true; |
7042
|
|
|
} |
7043
|
|
|
|
7044
|
|
|
function resetCache(){ |
|
|
|
|
7045
|
|
|
$this->_rulesets = null; |
7046
|
|
|
$this->_variables = null; |
7047
|
|
|
$this->lookups = array(); |
7048
|
|
|
} |
7049
|
|
|
|
7050
|
|
|
public function variables(){ |
7051
|
|
|
$this->_variables = array(); |
7052
|
|
|
foreach( $this->rules as $r){ |
7053
|
|
|
if ($r instanceof Less_Tree_Rule && $r->variable === true) { |
7054
|
|
|
$this->_variables[$r->name] = $r; |
7055
|
|
|
} |
7056
|
|
|
} |
7057
|
|
|
} |
7058
|
|
|
|
7059
|
|
|
public function variable($name){ |
7060
|
|
|
|
7061
|
|
|
if( is_null($this->_variables) ){ |
7062
|
|
|
$this->variables(); |
7063
|
|
|
} |
7064
|
|
|
return isset($this->_variables[$name]) ? $this->_variables[$name] : null; |
7065
|
|
|
} |
7066
|
|
|
|
7067
|
|
|
public function find( $selector, $self = null ){ |
7068
|
|
|
|
7069
|
|
|
$key = implode(' ',$selector->_oelements); |
7070
|
|
|
|
7071
|
|
|
if( !isset($this->lookups[$key]) ){ |
7072
|
|
|
|
7073
|
|
|
if( !$self ){ |
7074
|
|
|
$self = $this->ruleset_id; |
7075
|
|
|
} |
7076
|
|
|
|
7077
|
|
|
$this->lookups[$key] = array(); |
7078
|
|
|
|
7079
|
|
|
$first_oelement = $selector->_oelements[0]; |
7080
|
|
|
|
7081
|
|
|
foreach($this->rules as $rule){ |
7082
|
|
|
if( $rule instanceof Less_Tree_Ruleset && $rule->ruleset_id != $self ){ |
7083
|
|
|
|
7084
|
|
|
if( isset($rule->first_oelements[$first_oelement]) ){ |
7085
|
|
|
|
7086
|
|
|
foreach( $rule->selectors as $ruleSelector ){ |
7087
|
|
|
$match = $selector->match($ruleSelector); |
7088
|
|
|
if( $match ){ |
7089
|
|
|
if( $selector->elements_len > $match ){ |
7090
|
|
|
$this->lookups[$key] = array_merge($this->lookups[$key], $rule->find( new Less_Tree_Selector(array_slice($selector->elements, $match)), $self)); |
7091
|
|
|
} else { |
7092
|
|
|
$this->lookups[$key][] = $rule; |
7093
|
|
|
} |
7094
|
|
|
break; |
7095
|
|
|
} |
7096
|
|
|
} |
7097
|
|
|
} |
7098
|
|
|
} |
7099
|
|
|
} |
7100
|
|
|
} |
7101
|
|
|
|
7102
|
|
|
return $this->lookups[$key]; |
7103
|
|
|
} |
7104
|
|
|
|
7105
|
|
|
|
7106
|
|
|
/** |
7107
|
|
|
* @see Less_Tree::genCSS |
7108
|
|
|
*/ |
7109
|
|
|
public function genCSS( $output ){ |
7110
|
|
|
|
7111
|
|
|
if( !$this->root ){ |
7112
|
|
|
Less_Environment::$tabLevel++; |
7113
|
|
|
} |
7114
|
|
|
|
7115
|
|
|
$tabRuleStr = $tabSetStr = ''; |
7116
|
|
|
if( !Less_Parser::$options['compress'] ){ |
7117
|
|
|
if( Less_Environment::$tabLevel ){ |
7118
|
|
|
$tabRuleStr = "\n".str_repeat( ' ' , Less_Environment::$tabLevel ); |
7119
|
|
|
$tabSetStr = "\n".str_repeat( ' ' , Less_Environment::$tabLevel-1 ); |
7120
|
|
|
}else{ |
7121
|
|
|
$tabSetStr = $tabRuleStr = "\n"; |
7122
|
|
|
} |
7123
|
|
|
} |
7124
|
|
|
|
7125
|
|
|
|
7126
|
|
|
$ruleNodes = array(); |
7127
|
|
|
$rulesetNodes = array(); |
7128
|
|
|
foreach($this->rules as $rule){ |
7129
|
|
|
|
7130
|
|
|
$class = get_class($rule); |
7131
|
|
|
if( ($class === 'Less_Tree_Media') || ($class === 'Less_Tree_Directive') || ($this->root && $class === 'Less_Tree_Comment') || ($class === 'Less_Tree_Ruleset' && $rule->rules) ){ |
7132
|
|
|
$rulesetNodes[] = $rule; |
7133
|
|
|
}else{ |
7134
|
|
|
$ruleNodes[] = $rule; |
7135
|
|
|
} |
7136
|
|
|
} |
7137
|
|
|
|
7138
|
|
|
// If this is the root node, we don't render |
7139
|
|
|
// a selector, or {}. |
7140
|
|
|
if( !$this->root ){ |
7141
|
|
|
|
7142
|
|
|
/* |
7143
|
|
|
debugInfo = tree.debugInfo(env, this, tabSetStr); |
7144
|
|
|
|
7145
|
|
|
if (debugInfo) { |
7146
|
|
|
output.add(debugInfo); |
7147
|
|
|
output.add(tabSetStr); |
7148
|
|
|
} |
7149
|
|
|
*/ |
7150
|
|
|
|
7151
|
|
|
$paths_len = count($this->paths); |
7152
|
|
|
for( $i = 0; $i < $paths_len; $i++ ){ |
7153
|
|
|
$path = $this->paths[$i]; |
7154
|
|
|
$firstSelector = true; |
7155
|
|
|
|
7156
|
|
|
foreach($path as $p){ |
7157
|
|
|
$p->genCSS( $output, $firstSelector ); |
7158
|
|
|
$firstSelector = false; |
7159
|
|
|
} |
7160
|
|
|
|
7161
|
|
|
if( $i + 1 < $paths_len ){ |
7162
|
|
|
$output->add( ',' . $tabSetStr ); |
7163
|
|
|
} |
7164
|
|
|
} |
7165
|
|
|
|
7166
|
|
|
$output->add( (Less_Parser::$options['compress'] ? '{' : " {") . $tabRuleStr ); |
7167
|
|
|
} |
7168
|
|
|
|
7169
|
|
|
// Compile rules and rulesets |
7170
|
|
|
$ruleNodes_len = count($ruleNodes); |
7171
|
|
|
$rulesetNodes_len = count($rulesetNodes); |
7172
|
|
|
for( $i = 0; $i < $ruleNodes_len; $i++ ){ |
7173
|
|
|
$rule = $ruleNodes[$i]; |
7174
|
|
|
|
7175
|
|
|
// @page{ directive ends up with root elements inside it, a mix of rules and rulesets |
7176
|
|
|
// In this instance we do not know whether it is the last property |
7177
|
|
|
if( $i + 1 === $ruleNodes_len && (!$this->root || $rulesetNodes_len === 0 || $this->firstRoot ) ){ |
7178
|
|
|
Less_Environment::$lastRule = true; |
7179
|
|
|
} |
7180
|
|
|
|
7181
|
|
|
$rule->genCSS( $output ); |
7182
|
|
|
|
7183
|
|
|
if( !Less_Environment::$lastRule ){ |
7184
|
|
|
$output->add( $tabRuleStr ); |
7185
|
|
|
}else{ |
7186
|
|
|
Less_Environment::$lastRule = false; |
7187
|
|
|
} |
7188
|
|
|
} |
7189
|
|
|
|
7190
|
|
|
if( !$this->root ){ |
7191
|
|
|
$output->add( $tabSetStr . '}' ); |
7192
|
|
|
Less_Environment::$tabLevel--; |
7193
|
|
|
} |
7194
|
|
|
|
7195
|
|
|
$firstRuleset = true; |
7196
|
|
|
$space = ($this->root ? $tabRuleStr : $tabSetStr); |
7197
|
|
|
for( $i = 0; $i < $rulesetNodes_len; $i++ ){ |
7198
|
|
|
|
7199
|
|
|
if( $ruleNodes_len && $firstRuleset ){ |
7200
|
|
|
$output->add( $space ); |
7201
|
|
|
}elseif( !$firstRuleset ){ |
7202
|
|
|
$output->add( $space ); |
7203
|
|
|
} |
7204
|
|
|
$firstRuleset = false; |
7205
|
|
|
$rulesetNodes[$i]->genCSS( $output); |
7206
|
|
|
} |
7207
|
|
|
|
7208
|
|
|
if( !Less_Parser::$options['compress'] && $this->firstRoot ){ |
7209
|
|
|
$output->add( "\n" ); |
7210
|
|
|
} |
7211
|
|
|
|
7212
|
|
|
} |
7213
|
|
|
|
7214
|
|
|
|
7215
|
|
|
function markReferenced(){ |
|
|
|
|
7216
|
|
|
if( !$this->selectors ){ |
7217
|
|
|
return; |
7218
|
|
|
} |
7219
|
|
|
foreach($this->selectors as $selector){ |
7220
|
|
|
$selector->markReferenced(); |
7221
|
|
|
} |
7222
|
|
|
} |
7223
|
|
|
|
7224
|
|
|
public function joinSelectors( $context, $selectors ){ |
7225
|
|
|
$paths = array(); |
7226
|
|
|
if( is_array($selectors) ){ |
7227
|
|
|
foreach($selectors as $selector) { |
7228
|
|
|
$this->joinSelector( $paths, $context, $selector); |
7229
|
|
|
} |
7230
|
|
|
} |
7231
|
|
|
return $paths; |
7232
|
|
|
} |
7233
|
|
|
|
7234
|
|
|
public function joinSelector( &$paths, $context, $selector){ |
7235
|
|
|
|
7236
|
|
|
$hasParentSelector = false; |
7237
|
|
|
|
7238
|
|
|
foreach($selector->elements as $el) { |
7239
|
|
|
if( $el->value === '&') { |
7240
|
|
|
$hasParentSelector = true; |
7241
|
|
|
} |
7242
|
|
|
} |
7243
|
|
|
|
7244
|
|
|
if( !$hasParentSelector ){ |
7245
|
|
|
if( $context ){ |
7246
|
|
|
foreach($context as $context_el){ |
7247
|
|
|
$paths[] = array_merge($context_el, array($selector) ); |
7248
|
|
|
} |
7249
|
|
|
}else { |
7250
|
|
|
$paths[] = array($selector); |
7251
|
|
|
} |
7252
|
|
|
return; |
7253
|
|
|
} |
7254
|
|
|
|
7255
|
|
|
|
7256
|
|
|
// The paths are [[Selector]] |
7257
|
|
|
// The first list is a list of comma separated selectors |
7258
|
|
|
// The inner list is a list of inheritance separated selectors |
7259
|
|
|
// e.g. |
7260
|
|
|
// .a, .b { |
7261
|
|
|
// .c { |
7262
|
|
|
// } |
7263
|
|
|
// } |
7264
|
|
|
// == [[.a] [.c]] [[.b] [.c]] |
7265
|
|
|
// |
7266
|
|
|
|
7267
|
|
|
// the elements from the current selector so far |
7268
|
|
|
$currentElements = array(); |
7269
|
|
|
// the current list of new selectors to add to the path. |
7270
|
|
|
// We will build it up. We initiate it with one empty selector as we "multiply" the new selectors |
7271
|
|
|
// by the parents |
7272
|
|
|
$newSelectors = array(array()); |
7273
|
|
|
|
7274
|
|
|
|
7275
|
|
|
foreach( $selector->elements as $el){ |
7276
|
|
|
|
7277
|
|
|
// non parent reference elements just get added |
7278
|
|
|
if( $el->value !== '&' ){ |
7279
|
|
|
$currentElements[] = $el; |
7280
|
|
|
} else { |
7281
|
|
|
// the new list of selectors to add |
7282
|
|
|
$selectorsMultiplied = array(); |
7283
|
|
|
|
7284
|
|
|
// merge the current list of non parent selector elements |
7285
|
|
|
// on to the current list of selectors to add |
7286
|
|
|
if( $currentElements ){ |
|
|
|
|
7287
|
|
|
$this->mergeElementsOnToSelectors( $currentElements, $newSelectors); |
7288
|
|
|
} |
7289
|
|
|
|
7290
|
|
|
// loop through our current selectors |
7291
|
|
|
foreach($newSelectors as $sel){ |
7292
|
|
|
|
7293
|
|
|
// if we don't have any parent paths, the & might be in a mixin so that it can be used |
7294
|
|
|
// whether there are parents or not |
7295
|
|
|
if( !$context ){ |
7296
|
|
|
// the combinator used on el should now be applied to the next element instead so that |
7297
|
|
|
// it is not lost |
7298
|
|
|
if( $sel ){ |
7299
|
|
|
$sel[0]->elements = array_slice($sel[0]->elements,0); |
7300
|
|
|
$sel[0]->elements[] = new Less_Tree_Element($el->combinator, '', $el->index, $el->currentFileInfo ); |
7301
|
|
|
} |
7302
|
|
|
$selectorsMultiplied[] = $sel; |
7303
|
|
|
}else { |
7304
|
|
|
|
7305
|
|
|
// and the parent selectors |
7306
|
|
|
foreach($context as $parentSel){ |
7307
|
|
|
// We need to put the current selectors |
7308
|
|
|
// then join the last selector's elements on to the parents selectors |
7309
|
|
|
|
7310
|
|
|
// our new selector path |
7311
|
|
|
$newSelectorPath = array(); |
7312
|
|
|
// selectors from the parent after the join |
7313
|
|
|
$afterParentJoin = array(); |
7314
|
|
|
$newJoinedSelectorEmpty = true; |
7315
|
|
|
|
7316
|
|
|
//construct the joined selector - if & is the first thing this will be empty, |
7317
|
|
|
// if not newJoinedSelector will be the last set of elements in the selector |
7318
|
|
|
if( $sel ){ |
7319
|
|
|
$newSelectorPath = $sel; |
7320
|
|
|
$lastSelector = array_pop($newSelectorPath); |
7321
|
|
|
$newJoinedSelector = $selector->createDerived( array_slice($lastSelector->elements,0) ); |
7322
|
|
|
$newJoinedSelectorEmpty = false; |
7323
|
|
|
} |
7324
|
|
|
else { |
7325
|
|
|
$newJoinedSelector = $selector->createDerived(array()); |
7326
|
|
|
} |
7327
|
|
|
|
7328
|
|
|
//put together the parent selectors after the join |
7329
|
|
|
if ( count($parentSel) > 1) { |
7330
|
|
|
$afterParentJoin = array_merge($afterParentJoin, array_slice($parentSel,1) ); |
7331
|
|
|
} |
7332
|
|
|
|
7333
|
|
|
if ( $parentSel ){ |
7334
|
|
|
$newJoinedSelectorEmpty = false; |
7335
|
|
|
|
7336
|
|
|
// join the elements so far with the first part of the parent |
7337
|
|
|
$newJoinedSelector->elements[] = new Less_Tree_Element( $el->combinator, $parentSel[0]->elements[0]->value, $el->index, $el->currentFileInfo); |
7338
|
|
|
|
7339
|
|
|
$newJoinedSelector->elements = array_merge( $newJoinedSelector->elements, array_slice($parentSel[0]->elements, 1) ); |
7340
|
|
|
} |
7341
|
|
|
|
7342
|
|
|
if (!$newJoinedSelectorEmpty) { |
7343
|
|
|
// now add the joined selector |
7344
|
|
|
$newSelectorPath[] = $newJoinedSelector; |
7345
|
|
|
} |
7346
|
|
|
|
7347
|
|
|
// and the rest of the parent |
7348
|
|
|
$newSelectorPath = array_merge($newSelectorPath, $afterParentJoin); |
7349
|
|
|
|
7350
|
|
|
// add that to our new set of selectors |
7351
|
|
|
$selectorsMultiplied[] = $newSelectorPath; |
7352
|
|
|
} |
7353
|
|
|
} |
7354
|
|
|
} |
7355
|
|
|
|
7356
|
|
|
// our new selectors has been multiplied, so reset the state |
7357
|
|
|
$newSelectors = $selectorsMultiplied; |
7358
|
|
|
$currentElements = array(); |
7359
|
|
|
} |
7360
|
|
|
} |
7361
|
|
|
|
7362
|
|
|
// if we have any elements left over (e.g. .a& .b == .b) |
7363
|
|
|
// add them on to all the current selectors |
7364
|
|
|
if( $currentElements ){ |
|
|
|
|
7365
|
|
|
$this->mergeElementsOnToSelectors($currentElements, $newSelectors); |
7366
|
|
|
} |
7367
|
|
|
foreach( $newSelectors as $new_sel){ |
7368
|
|
|
if( $new_sel ){ |
7369
|
|
|
$paths[] = $new_sel; |
7370
|
|
|
} |
7371
|
|
|
} |
7372
|
|
|
} |
7373
|
|
|
|
7374
|
|
|
function mergeElementsOnToSelectors( $elements, &$selectors){ |
|
|
|
|
7375
|
|
|
|
7376
|
|
|
if( !$selectors ){ |
7377
|
|
|
$selectors[] = array( new Less_Tree_Selector($elements) ); |
7378
|
|
|
return; |
7379
|
|
|
} |
7380
|
|
|
|
7381
|
|
|
|
7382
|
|
|
foreach( $selectors as &$sel){ |
7383
|
|
|
|
7384
|
|
|
// if the previous thing in sel is a parent this needs to join on to it |
7385
|
|
|
if( $sel ){ |
7386
|
|
|
$last = count($sel)-1; |
7387
|
|
|
$sel[$last] = $sel[$last]->createDerived( array_merge($sel[$last]->elements, $elements) ); |
7388
|
|
|
}else{ |
7389
|
|
|
$sel[] = new Less_Tree_Selector( $elements ); |
7390
|
|
|
} |
7391
|
|
|
} |
7392
|
|
|
} |
7393
|
|
|
} |
7394
|
|
|
|
7395
|
|
|
|
7396
|
|
|
/** |
7397
|
|
|
* RulesetCall |
7398
|
|
|
* |
7399
|
|
|
* @package Less |
7400
|
|
|
* @subpackage tree |
7401
|
|
|
*/ |
7402
|
|
|
class Less_Tree_RulesetCall extends Less_Tree{ |
7403
|
|
|
|
7404
|
|
|
public $variable; |
7405
|
|
|
public $type = "RulesetCall"; |
7406
|
|
|
|
7407
|
|
|
public function __construct($variable){ |
7408
|
|
|
$this->variable = $variable; |
7409
|
|
|
} |
7410
|
|
|
|
7411
|
|
|
public function accept($visitor) {} |
7412
|
|
|
|
7413
|
|
|
public function compile( $env ){ |
7414
|
|
|
$variable = new Less_Tree_Variable($this->variable); |
7415
|
|
|
$detachedRuleset = $variable->compile($env); |
7416
|
|
|
return $detachedRuleset->callEval($env); |
7417
|
|
|
} |
7418
|
|
|
} |
7419
|
|
|
|
7420
|
|
|
|
7421
|
|
|
|
7422
|
|
|
/** |
7423
|
|
|
* Selector |
7424
|
|
|
* |
7425
|
|
|
* @package Less |
7426
|
|
|
* @subpackage tree |
7427
|
|
|
*/ |
7428
|
|
|
class Less_Tree_Selector extends Less_Tree{ |
7429
|
|
|
|
7430
|
|
|
public $elements; |
7431
|
|
|
public $condition; |
7432
|
|
|
public $extendList = array(); |
7433
|
|
|
public $_css; |
7434
|
|
|
public $index; |
7435
|
|
|
public $evaldCondition = false; |
7436
|
|
|
public $type = 'Selector'; |
7437
|
|
|
public $currentFileInfo = array(); |
7438
|
|
|
public $isReferenced; |
7439
|
|
|
public $mediaEmpty; |
7440
|
|
|
|
7441
|
|
|
public $elements_len = 0; |
7442
|
|
|
|
7443
|
|
|
public $_oelements; |
7444
|
|
|
public $_oelements_len; |
7445
|
|
|
public $cacheable = true; |
7446
|
|
|
|
7447
|
|
|
/** |
7448
|
|
|
* @param boolean $isReferenced |
7449
|
|
|
*/ |
7450
|
|
|
public function __construct( $elements, $extendList = array() , $condition = null, $index=null, $currentFileInfo=null, $isReferenced=null ){ |
7451
|
|
|
|
7452
|
|
|
$this->elements = $elements; |
7453
|
|
|
$this->elements_len = count($elements); |
7454
|
|
|
$this->extendList = $extendList; |
7455
|
|
|
$this->condition = $condition; |
7456
|
|
|
if( $currentFileInfo ){ |
7457
|
|
|
$this->currentFileInfo = $currentFileInfo; |
7458
|
|
|
} |
7459
|
|
|
$this->isReferenced = $isReferenced; |
7460
|
|
|
if( !$condition ){ |
7461
|
|
|
$this->evaldCondition = true; |
7462
|
|
|
} |
7463
|
|
|
|
7464
|
|
|
$this->CacheElements(); |
7465
|
|
|
} |
7466
|
|
|
|
7467
|
|
|
public function accept($visitor) { |
7468
|
|
|
$this->elements = $visitor->visitArray($this->elements); |
7469
|
|
|
$this->extendList = $visitor->visitArray($this->extendList); |
7470
|
|
|
if( $this->condition ){ |
7471
|
|
|
$this->condition = $visitor->visitObj($this->condition); |
7472
|
|
|
} |
7473
|
|
|
|
7474
|
|
|
if( $visitor instanceof Less_Visitor_extendFinder ){ |
7475
|
|
|
$this->CacheElements(); |
7476
|
|
|
} |
7477
|
|
|
} |
7478
|
|
|
|
7479
|
|
|
public function createDerived( $elements, $extendList = null, $evaldCondition = null ){ |
7480
|
|
|
$newSelector = new Less_Tree_Selector( $elements, ($extendList ? $extendList : $this->extendList), null, $this->index, $this->currentFileInfo, $this->isReferenced); |
7481
|
|
|
$newSelector->evaldCondition = $evaldCondition ? $evaldCondition : $this->evaldCondition; |
7482
|
|
|
return $newSelector; |
7483
|
|
|
} |
7484
|
|
|
|
7485
|
|
|
|
7486
|
|
|
public function match( $other ){ |
7487
|
|
|
|
7488
|
|
|
if( !$other->_oelements || ($this->elements_len < $other->_oelements_len) ){ |
7489
|
|
|
return 0; |
7490
|
|
|
} |
7491
|
|
|
|
7492
|
|
|
for( $i = 0; $i < $other->_oelements_len; $i++ ){ |
7493
|
|
|
if( $this->elements[$i]->value !== $other->_oelements[$i]) { |
7494
|
|
|
return 0; |
7495
|
|
|
} |
7496
|
|
|
} |
7497
|
|
|
|
7498
|
|
|
return $other->_oelements_len; // return number of matched elements |
7499
|
|
|
} |
7500
|
|
|
|
7501
|
|
|
|
7502
|
|
|
public function CacheElements(){ |
7503
|
|
|
|
7504
|
|
|
$this->_oelements = array(); |
7505
|
|
|
$css = ''; |
7506
|
|
|
|
7507
|
|
|
foreach($this->elements as $v){ |
7508
|
|
|
|
7509
|
|
|
$css .= $v->combinator; |
7510
|
|
|
if( !$v->value_is_object ){ |
7511
|
|
|
$css .= $v->value; |
7512
|
|
|
continue; |
7513
|
|
|
} |
7514
|
|
|
|
7515
|
|
|
if( !property_exists($v->value,'value') || !is_string($v->value->value) ){ |
7516
|
|
|
$this->cacheable = false; |
7517
|
|
|
return; |
7518
|
|
|
} |
7519
|
|
|
$css .= $v->value->value; |
7520
|
|
|
} |
7521
|
|
|
|
7522
|
|
|
$this->_oelements_len = preg_match_all('/[,&#\.\w-](?:[\w-]|(?:\\\\.))*/', $css, $matches); |
7523
|
|
|
if( $this->_oelements_len ){ |
7524
|
|
|
$this->_oelements = $matches[0]; |
7525
|
|
|
|
7526
|
|
|
if( $this->_oelements[0] === '&' ){ |
7527
|
|
|
array_shift($this->_oelements); |
7528
|
|
|
$this->_oelements_len--; |
7529
|
|
|
} |
7530
|
|
|
} |
7531
|
|
|
} |
7532
|
|
|
|
7533
|
|
|
public function isJustParentSelector(){ |
7534
|
|
|
return !$this->mediaEmpty && |
7535
|
|
|
count($this->elements) === 1 && |
7536
|
|
|
$this->elements[0]->value === '&' && |
7537
|
|
|
($this->elements[0]->combinator === ' ' || $this->elements[0]->combinator === ''); |
7538
|
|
|
} |
7539
|
|
|
|
7540
|
|
|
public function compile($env) { |
7541
|
|
|
|
7542
|
|
|
$elements = array(); |
7543
|
|
|
foreach($this->elements as $el){ |
7544
|
|
|
$elements[] = $el->compile($env); |
7545
|
|
|
} |
7546
|
|
|
|
7547
|
|
|
$extendList = array(); |
7548
|
|
|
foreach($this->extendList as $el){ |
7549
|
|
|
$extendList[] = $el->compile($el); |
7550
|
|
|
} |
7551
|
|
|
|
7552
|
|
|
$evaldCondition = false; |
7553
|
|
|
if( $this->condition ){ |
7554
|
|
|
$evaldCondition = $this->condition->compile($env); |
7555
|
|
|
} |
7556
|
|
|
|
7557
|
|
|
return $this->createDerived( $elements, $extendList, $evaldCondition ); |
7558
|
|
|
} |
7559
|
|
|
|
7560
|
|
|
|
7561
|
|
|
/** |
7562
|
|
|
* @see Less_Tree::genCSS |
7563
|
|
|
*/ |
7564
|
|
|
public function genCSS( $output, $firstSelector = true ){ |
7565
|
|
|
|
7566
|
|
|
if( !$firstSelector && $this->elements[0]->combinator === "" ){ |
7567
|
|
|
$output->add(' ', $this->currentFileInfo, $this->index); |
7568
|
|
|
} |
7569
|
|
|
|
7570
|
|
|
foreach($this->elements as $element){ |
7571
|
|
|
$element->genCSS( $output ); |
7572
|
|
|
} |
7573
|
|
|
} |
7574
|
|
|
|
7575
|
|
|
public function markReferenced(){ |
7576
|
|
|
$this->isReferenced = true; |
7577
|
|
|
} |
7578
|
|
|
|
7579
|
|
|
public function getIsReferenced(){ |
7580
|
|
|
return !isset($this->currentFileInfo['reference']) || !$this->currentFileInfo['reference'] || $this->isReferenced; |
7581
|
|
|
} |
7582
|
|
|
|
7583
|
|
|
public function getIsOutput(){ |
7584
|
|
|
return $this->evaldCondition; |
7585
|
|
|
} |
7586
|
|
|
|
7587
|
|
|
} |
7588
|
|
|
|
7589
|
|
|
|
7590
|
|
|
/** |
7591
|
|
|
* UnicodeDescriptor |
7592
|
|
|
* |
7593
|
|
|
* @package Less |
7594
|
|
|
* @subpackage tree |
7595
|
|
|
*/ |
7596
|
|
|
class Less_Tree_UnicodeDescriptor extends Less_Tree{ |
7597
|
|
|
|
7598
|
|
|
public $value; |
7599
|
|
|
public $type = 'UnicodeDescriptor'; |
7600
|
|
|
|
7601
|
|
|
public function __construct($value){ |
7602
|
|
|
$this->value = $value; |
7603
|
|
|
} |
7604
|
|
|
|
7605
|
|
|
/** |
7606
|
|
|
* @see Less_Tree::genCSS |
7607
|
|
|
*/ |
7608
|
|
|
public function genCSS( $output ){ |
7609
|
|
|
$output->add( $this->value ); |
7610
|
|
|
} |
7611
|
|
|
|
7612
|
|
|
public function compile(){ |
7613
|
|
|
return $this; |
7614
|
|
|
} |
7615
|
|
|
} |
7616
|
|
|
|
7617
|
|
|
|
7618
|
|
|
|
7619
|
|
|
/** |
7620
|
|
|
* Unit |
7621
|
|
|
* |
7622
|
|
|
* @package Less |
7623
|
|
|
* @subpackage tree |
7624
|
|
|
*/ |
7625
|
|
|
class Less_Tree_Unit extends Less_Tree{ |
7626
|
|
|
|
7627
|
|
|
var $numerator = array(); |
7628
|
|
|
var $denominator = array(); |
7629
|
|
|
public $backupUnit; |
7630
|
|
|
public $type = 'Unit'; |
7631
|
|
|
|
7632
|
|
|
public function __construct($numerator = array(), $denominator = array(), $backupUnit = null ){ |
7633
|
|
|
$this->numerator = $numerator; |
7634
|
|
|
$this->denominator = $denominator; |
7635
|
|
|
$this->backupUnit = $backupUnit; |
7636
|
|
|
} |
7637
|
|
|
|
7638
|
|
|
public function __clone(){ |
7639
|
|
|
} |
7640
|
|
|
|
7641
|
|
|
/** |
7642
|
|
|
* @see Less_Tree::genCSS |
7643
|
|
|
*/ |
7644
|
|
|
public function genCSS( $output ){ |
7645
|
|
|
|
7646
|
|
|
if( $this->numerator ){ |
|
|
|
|
7647
|
|
|
$output->add( $this->numerator[0] ); |
7648
|
|
|
}elseif( $this->denominator ){ |
|
|
|
|
7649
|
|
|
$output->add( $this->denominator[0] ); |
7650
|
|
|
}elseif( !Less_Parser::$options['strictUnits'] && $this->backupUnit ){ |
7651
|
|
|
$output->add( $this->backupUnit ); |
7652
|
|
|
return ; |
7653
|
|
|
} |
7654
|
|
|
} |
7655
|
|
|
|
7656
|
|
|
public function toString(){ |
7657
|
|
|
$returnStr = implode('*',$this->numerator); |
7658
|
|
|
foreach($this->denominator as $d){ |
7659
|
|
|
$returnStr .= '/'.$d; |
7660
|
|
|
} |
7661
|
|
|
return $returnStr; |
7662
|
|
|
} |
7663
|
|
|
|
7664
|
|
|
public function __toString(){ |
7665
|
|
|
return $this->toString(); |
7666
|
|
|
} |
7667
|
|
|
|
7668
|
|
|
|
7669
|
|
|
/** |
7670
|
|
|
* @param Less_Tree_Unit $other |
7671
|
|
|
*/ |
7672
|
|
|
public function compare($other) { |
7673
|
|
|
return $this->is( $other->toString() ) ? 0 : -1; |
7674
|
|
|
} |
7675
|
|
|
|
7676
|
|
|
public function is($unitString){ |
7677
|
|
|
return $this->toString() === $unitString; |
7678
|
|
|
} |
7679
|
|
|
|
7680
|
|
|
public function isLength(){ |
7681
|
|
|
$css = $this->toCSS(); |
7682
|
|
|
return !!preg_match('/px|em|%|in|cm|mm|pc|pt|ex/',$css); |
7683
|
|
|
} |
7684
|
|
|
|
7685
|
|
|
public function isAngle() { |
7686
|
|
|
return isset( Less_Tree_UnitConversions::$angle[$this->toCSS()] ); |
7687
|
|
|
} |
7688
|
|
|
|
7689
|
|
|
public function isEmpty(){ |
7690
|
|
|
return !$this->numerator && !$this->denominator; |
|
|
|
|
7691
|
|
|
} |
7692
|
|
|
|
7693
|
|
|
public function isSingular() { |
7694
|
|
|
return count($this->numerator) <= 1 && !$this->denominator; |
|
|
|
|
7695
|
|
|
} |
7696
|
|
|
|
7697
|
|
|
|
7698
|
|
|
public function usedUnits(){ |
7699
|
|
|
$result = array(); |
7700
|
|
|
|
7701
|
|
|
foreach(Less_Tree_UnitConversions::$groups as $groupName){ |
7702
|
|
|
$group = Less_Tree_UnitConversions::${$groupName}; |
7703
|
|
|
|
7704
|
|
View Code Duplication |
foreach($this->numerator as $atomicUnit){ |
7705
|
|
|
if( isset($group[$atomicUnit]) && !isset($result[$groupName]) ){ |
7706
|
|
|
$result[$groupName] = $atomicUnit; |
7707
|
|
|
} |
7708
|
|
|
} |
7709
|
|
|
|
7710
|
|
View Code Duplication |
foreach($this->denominator as $atomicUnit){ |
7711
|
|
|
if( isset($group[$atomicUnit]) && !isset($result[$groupName]) ){ |
7712
|
|
|
$result[$groupName] = $atomicUnit; |
7713
|
|
|
} |
7714
|
|
|
} |
7715
|
|
|
} |
7716
|
|
|
|
7717
|
|
|
return $result; |
7718
|
|
|
} |
7719
|
|
|
|
7720
|
|
|
public function cancel(){ |
7721
|
|
|
$counter = array(); |
7722
|
|
|
$backup = null; |
7723
|
|
|
|
7724
|
|
View Code Duplication |
foreach($this->numerator as $atomicUnit){ |
7725
|
|
|
if( !$backup ){ |
7726
|
|
|
$backup = $atomicUnit; |
7727
|
|
|
} |
7728
|
|
|
$counter[$atomicUnit] = ( isset($counter[$atomicUnit]) ? $counter[$atomicUnit] : 0) + 1; |
7729
|
|
|
} |
7730
|
|
|
|
7731
|
|
View Code Duplication |
foreach($this->denominator as $atomicUnit){ |
7732
|
|
|
if( !$backup ){ |
7733
|
|
|
$backup = $atomicUnit; |
7734
|
|
|
} |
7735
|
|
|
$counter[$atomicUnit] = ( isset($counter[$atomicUnit]) ? $counter[$atomicUnit] : 0) - 1; |
7736
|
|
|
} |
7737
|
|
|
|
7738
|
|
|
$this->numerator = array(); |
7739
|
|
|
$this->denominator = array(); |
7740
|
|
|
|
7741
|
|
|
foreach($counter as $atomicUnit => $count){ |
7742
|
|
|
if( $count > 0 ){ |
7743
|
|
|
for( $i = 0; $i < $count; $i++ ){ |
7744
|
|
|
$this->numerator[] = $atomicUnit; |
7745
|
|
|
} |
7746
|
|
|
}elseif( $count < 0 ){ |
7747
|
|
|
for( $i = 0; $i < -$count; $i++ ){ |
7748
|
|
|
$this->denominator[] = $atomicUnit; |
7749
|
|
|
} |
7750
|
|
|
} |
7751
|
|
|
} |
7752
|
|
|
|
7753
|
|
|
if( !$this->numerator && !$this->denominator && $backup ){ |
|
|
|
|
7754
|
|
|
$this->backupUnit = $backup; |
7755
|
|
|
} |
7756
|
|
|
|
7757
|
|
|
sort($this->numerator); |
7758
|
|
|
sort($this->denominator); |
7759
|
|
|
} |
7760
|
|
|
|
7761
|
|
|
|
7762
|
|
|
} |
7763
|
|
|
|
7764
|
|
|
|
7765
|
|
|
|
7766
|
|
|
/** |
7767
|
|
|
* UnitConversions |
7768
|
|
|
* |
7769
|
|
|
* @package Less |
7770
|
|
|
* @subpackage tree |
7771
|
|
|
*/ |
7772
|
|
|
class Less_Tree_UnitConversions{ |
7773
|
|
|
|
7774
|
|
|
public static $groups = array('length','duration','angle'); |
7775
|
|
|
|
7776
|
|
|
public static $length = array( |
7777
|
|
|
'm'=> 1, |
7778
|
|
|
'cm'=> 0.01, |
7779
|
|
|
'mm'=> 0.001, |
7780
|
|
|
'in'=> 0.0254, |
7781
|
|
|
'px'=> 0.000264583, // 0.0254 / 96, |
7782
|
|
|
'pt'=> 0.000352778, // 0.0254 / 72, |
7783
|
|
|
'pc'=> 0.004233333, // 0.0254 / 72 * 12 |
7784
|
|
|
); |
7785
|
|
|
|
7786
|
|
|
public static $duration = array( |
7787
|
|
|
's'=> 1, |
7788
|
|
|
'ms'=> 0.001 |
7789
|
|
|
); |
7790
|
|
|
|
7791
|
|
|
public static $angle = array( |
7792
|
|
|
'rad' => 0.1591549430919, // 1/(2*M_PI), |
7793
|
|
|
'deg' => 0.002777778, // 1/360, |
7794
|
|
|
'grad'=> 0.0025, // 1/400, |
7795
|
|
|
'turn'=> 1 |
7796
|
|
|
); |
7797
|
|
|
|
7798
|
|
|
} |
7799
|
|
|
|
7800
|
|
|
/** |
7801
|
|
|
* Url |
7802
|
|
|
* |
7803
|
|
|
* @package Less |
7804
|
|
|
* @subpackage tree |
7805
|
|
|
*/ |
7806
|
|
|
class Less_Tree_Url extends Less_Tree{ |
7807
|
|
|
|
7808
|
|
|
public $attrs; |
7809
|
|
|
public $value; |
7810
|
|
|
public $currentFileInfo; |
7811
|
|
|
public $isEvald; |
7812
|
|
|
public $type = 'Url'; |
7813
|
|
|
|
7814
|
|
|
public function __construct($value, $currentFileInfo = null, $isEvald = null){ |
7815
|
|
|
$this->value = $value; |
7816
|
|
|
$this->currentFileInfo = $currentFileInfo; |
7817
|
|
|
$this->isEvald = $isEvald; |
7818
|
|
|
} |
7819
|
|
|
|
7820
|
|
|
public function accept( $visitor ){ |
7821
|
|
|
$this->value = $visitor->visitObj($this->value); |
7822
|
|
|
} |
7823
|
|
|
|
7824
|
|
|
/** |
7825
|
|
|
* @see Less_Tree::genCSS |
7826
|
|
|
*/ |
7827
|
|
|
public function genCSS( $output ){ |
7828
|
|
|
$output->add( 'url(' ); |
7829
|
|
|
$this->value->genCSS( $output ); |
7830
|
|
|
$output->add( ')' ); |
7831
|
|
|
} |
7832
|
|
|
|
7833
|
|
|
/** |
7834
|
|
|
* @param Less_Functions $ctx |
7835
|
|
|
*/ |
7836
|
|
|
public function compile($ctx){ |
7837
|
|
|
$val = $this->value->compile($ctx); |
7838
|
|
|
|
7839
|
|
|
if( !$this->isEvald ){ |
7840
|
|
|
// Add the base path if the URL is relative |
7841
|
|
|
if( Less_Parser::$options['relativeUrls'] |
7842
|
|
|
&& $this->currentFileInfo |
7843
|
|
|
&& is_string($val->value) |
7844
|
|
|
&& Less_Environment::isPathRelative($val->value) |
7845
|
|
|
){ |
7846
|
|
|
$rootpath = $this->currentFileInfo['uri_root']; |
7847
|
|
|
if ( !$val->quote ){ |
7848
|
|
|
$rootpath = preg_replace('/[\(\)\'"\s]/', '\\$1', $rootpath ); |
7849
|
|
|
} |
7850
|
|
|
$val->value = $rootpath . $val->value; |
7851
|
|
|
} |
7852
|
|
|
|
7853
|
|
|
$val->value = Less_Environment::normalizePath( $val->value); |
7854
|
|
|
} |
7855
|
|
|
|
7856
|
|
|
// Add cache buster if enabled |
7857
|
|
|
if( Less_Parser::$options['urlArgs'] ){ |
7858
|
|
|
if( !preg_match('/^\s*data:/',$val->value) ){ |
7859
|
|
|
$delimiter = strpos($val->value,'?') === false ? '?' : '&'; |
7860
|
|
|
$urlArgs = $delimiter . Less_Parser::$options['urlArgs']; |
7861
|
|
|
$hash_pos = strpos($val->value,'#'); |
7862
|
|
|
if( $hash_pos !== false ){ |
7863
|
|
|
$val->value = substr_replace($val->value,$urlArgs, $hash_pos, 0); |
7864
|
|
|
} else { |
7865
|
|
|
$val->value .= $urlArgs; |
7866
|
|
|
} |
7867
|
|
|
} |
7868
|
|
|
} |
7869
|
|
|
|
7870
|
|
|
return new Less_Tree_URL($val, $this->currentFileInfo, true); |
7871
|
|
|
} |
7872
|
|
|
|
7873
|
|
|
} |
7874
|
|
|
|
7875
|
|
|
|
7876
|
|
|
/** |
7877
|
|
|
* Value |
7878
|
|
|
* |
7879
|
|
|
* @package Less |
7880
|
|
|
* @subpackage tree |
7881
|
|
|
*/ |
7882
|
|
|
class Less_Tree_Value extends Less_Tree{ |
7883
|
|
|
|
7884
|
|
|
public $type = 'Value'; |
7885
|
|
|
public $value; |
7886
|
|
|
|
7887
|
|
|
public function __construct($value){ |
7888
|
|
|
$this->value = $value; |
7889
|
|
|
} |
7890
|
|
|
|
7891
|
|
|
public function accept($visitor) { |
7892
|
|
|
$this->value = $visitor->visitArray($this->value); |
7893
|
|
|
} |
7894
|
|
|
|
7895
|
|
|
public function compile($env){ |
7896
|
|
|
|
7897
|
|
|
$ret = array(); |
7898
|
|
|
$i = 0; |
7899
|
|
|
foreach($this->value as $i => $v){ |
7900
|
|
|
$ret[] = $v->compile($env); |
7901
|
|
|
} |
7902
|
|
|
if( $i > 0 ){ |
7903
|
|
|
return new Less_Tree_Value($ret); |
7904
|
|
|
} |
7905
|
|
|
return $ret[0]; |
7906
|
|
|
} |
7907
|
|
|
|
7908
|
|
|
/** |
7909
|
|
|
* @see Less_Tree::genCSS |
7910
|
|
|
*/ |
7911
|
|
View Code Duplication |
function genCSS( $output ){ |
|
|
|
|
7912
|
|
|
$len = count($this->value); |
7913
|
|
|
for($i = 0; $i < $len; $i++ ){ |
7914
|
|
|
$this->value[$i]->genCSS( $output ); |
7915
|
|
|
if( $i+1 < $len ){ |
7916
|
|
|
$output->add( Less_Environment::$_outputMap[','] ); |
7917
|
|
|
} |
7918
|
|
|
} |
7919
|
|
|
} |
7920
|
|
|
|
7921
|
|
|
} |
7922
|
|
|
|
7923
|
|
|
|
7924
|
|
|
/** |
7925
|
|
|
* Variable |
7926
|
|
|
* |
7927
|
|
|
* @package Less |
7928
|
|
|
* @subpackage tree |
7929
|
|
|
*/ |
7930
|
|
|
class Less_Tree_Variable extends Less_Tree{ |
7931
|
|
|
|
7932
|
|
|
public $name; |
7933
|
|
|
public $index; |
7934
|
|
|
public $currentFileInfo; |
7935
|
|
|
public $evaluating = false; |
7936
|
|
|
public $type = 'Variable'; |
7937
|
|
|
|
7938
|
|
|
/** |
7939
|
|
|
* @param string $name |
7940
|
|
|
*/ |
7941
|
|
|
public function __construct($name, $index = null, $currentFileInfo = null) { |
7942
|
|
|
$this->name = $name; |
7943
|
|
|
$this->index = $index; |
7944
|
|
|
$this->currentFileInfo = $currentFileInfo; |
7945
|
|
|
} |
7946
|
|
|
|
7947
|
|
|
public function compile($env) { |
7948
|
|
|
|
7949
|
|
|
if( $this->name[1] === '@' ){ |
7950
|
|
|
$v = new Less_Tree_Variable(substr($this->name, 1), $this->index + 1, $this->currentFileInfo); |
7951
|
|
|
$name = '@' . $v->compile($env)->value; |
7952
|
|
|
}else{ |
7953
|
|
|
$name = $this->name; |
7954
|
|
|
} |
7955
|
|
|
|
7956
|
|
|
if ($this->evaluating) { |
7957
|
|
|
throw new Less_Exception_Compiler("Recursive variable definition for " . $name, null, $this->index, $this->currentFileInfo); |
7958
|
|
|
} |
7959
|
|
|
|
7960
|
|
|
$this->evaluating = true; |
7961
|
|
|
|
7962
|
|
|
foreach($env->frames as $frame){ |
7963
|
|
|
if( $v = $frame->variable($name) ){ |
7964
|
|
|
$r = $v->value->compile($env); |
7965
|
|
|
$this->evaluating = false; |
7966
|
|
|
return $r; |
7967
|
|
|
} |
7968
|
|
|
} |
7969
|
|
|
|
7970
|
|
|
throw new Less_Exception_Compiler("variable " . $name . " is undefined in file ".$this->currentFileInfo["filename"], null, $this->index, $this->currentFileInfo); |
7971
|
|
|
} |
7972
|
|
|
|
7973
|
|
|
} |
7974
|
|
|
|
7975
|
|
|
|
7976
|
|
|
|
7977
|
|
|
class Less_Tree_Mixin_Call extends Less_Tree{ |
7978
|
|
|
|
7979
|
|
|
public $selector; |
7980
|
|
|
public $arguments; |
7981
|
|
|
public $index; |
7982
|
|
|
public $currentFileInfo; |
7983
|
|
|
|
7984
|
|
|
public $important; |
7985
|
|
|
public $type = 'MixinCall'; |
7986
|
|
|
|
7987
|
|
|
/** |
7988
|
|
|
* less.js: tree.mixin.Call |
7989
|
|
|
* |
7990
|
|
|
*/ |
7991
|
|
|
public function __construct($elements, $args, $index, $currentFileInfo, $important = false){ |
7992
|
|
|
$this->selector = new Less_Tree_Selector($elements); |
7993
|
|
|
$this->arguments = $args; |
7994
|
|
|
$this->index = $index; |
7995
|
|
|
$this->currentFileInfo = $currentFileInfo; |
7996
|
|
|
$this->important = $important; |
7997
|
|
|
} |
7998
|
|
|
|
7999
|
|
|
//function accept($visitor){ |
8000
|
|
|
// $this->selector = $visitor->visit($this->selector); |
8001
|
|
|
// $this->arguments = $visitor->visit($this->arguments); |
8002
|
|
|
//} |
8003
|
|
|
|
8004
|
|
|
|
8005
|
|
|
public function compile($env){ |
8006
|
|
|
|
8007
|
|
|
$rules = array(); |
8008
|
|
|
$match = false; |
8009
|
|
|
$isOneFound = false; |
8010
|
|
|
$candidates = array(); |
8011
|
|
|
$defaultUsed = false; |
8012
|
|
|
$conditionResult = array(); |
8013
|
|
|
|
8014
|
|
|
$args = array(); |
8015
|
|
|
foreach($this->arguments as $a){ |
8016
|
|
|
$args[] = array('name'=> $a['name'], 'value' => $a['value']->compile($env) ); |
8017
|
|
|
} |
8018
|
|
|
|
8019
|
|
|
foreach($env->frames as $frame){ |
8020
|
|
|
|
8021
|
|
|
$mixins = $frame->find($this->selector); |
8022
|
|
|
|
8023
|
|
|
if( !$mixins ){ |
8024
|
|
|
continue; |
8025
|
|
|
} |
8026
|
|
|
|
8027
|
|
|
$isOneFound = true; |
8028
|
|
|
$defNone = 0; |
8029
|
|
|
$defTrue = 1; |
8030
|
|
|
$defFalse = 2; |
8031
|
|
|
|
8032
|
|
|
// To make `default()` function independent of definition order we have two "subpasses" here. |
8033
|
|
|
// At first we evaluate each guard *twice* (with `default() == true` and `default() == false`), |
8034
|
|
|
// and build candidate list with corresponding flags. Then, when we know all possible matches, |
8035
|
|
|
// we make a final decision. |
8036
|
|
|
|
8037
|
|
|
$mixins_len = count($mixins); |
8038
|
|
|
for( $m = 0; $m < $mixins_len; $m++ ){ |
8039
|
|
|
$mixin = $mixins[$m]; |
8040
|
|
|
|
8041
|
|
|
if( $this->IsRecursive( $env, $mixin ) ){ |
8042
|
|
|
continue; |
8043
|
|
|
} |
8044
|
|
|
|
8045
|
|
|
if( $mixin->matchArgs($args, $env) ){ |
8046
|
|
|
|
8047
|
|
|
$candidate = array('mixin' => $mixin, 'group' => $defNone); |
8048
|
|
|
|
8049
|
|
|
if( $mixin instanceof Less_Tree_Ruleset ){ |
8050
|
|
|
|
8051
|
|
|
for( $f = 0; $f < 2; $f++ ){ |
8052
|
|
|
Less_Tree_DefaultFunc::value($f); |
8053
|
|
|
$conditionResult[$f] = $mixin->matchCondition( $args, $env); |
8054
|
|
|
} |
8055
|
|
|
if( $conditionResult[0] || $conditionResult[1] ){ |
8056
|
|
|
if( $conditionResult[0] != $conditionResult[1] ){ |
8057
|
|
|
$candidate['group'] = $conditionResult[1] ? $defTrue : $defFalse; |
8058
|
|
|
} |
8059
|
|
|
|
8060
|
|
|
$candidates[] = $candidate; |
8061
|
|
|
} |
8062
|
|
|
}else{ |
8063
|
|
|
$candidates[] = $candidate; |
8064
|
|
|
} |
8065
|
|
|
|
8066
|
|
|
$match = true; |
8067
|
|
|
} |
8068
|
|
|
} |
8069
|
|
|
|
8070
|
|
|
Less_Tree_DefaultFunc::reset(); |
8071
|
|
|
|
8072
|
|
|
|
8073
|
|
|
$count = array(0, 0, 0); |
8074
|
|
|
for( $m = 0; $m < count($candidates); $m++ ){ |
|
|
|
|
8075
|
|
|
$count[ $candidates[$m]['group'] ]++; |
8076
|
|
|
} |
8077
|
|
|
|
8078
|
|
|
if( $count[$defNone] > 0 ){ |
8079
|
|
|
$defaultResult = $defFalse; |
8080
|
|
|
} else { |
8081
|
|
|
$defaultResult = $defTrue; |
8082
|
|
|
if( ($count[$defTrue] + $count[$defFalse]) > 1 ){ |
8083
|
|
|
throw new Exception( 'Ambiguous use of `default()` found when matching for `'. $this->format($args) + '`' ); |
8084
|
|
|
} |
8085
|
|
|
} |
8086
|
|
|
|
8087
|
|
|
|
8088
|
|
|
$candidates_length = count($candidates); |
8089
|
|
|
$length_1 = ($candidates_length == 1); |
8090
|
|
|
|
8091
|
|
|
for( $m = 0; $m < $candidates_length; $m++){ |
8092
|
|
|
$candidate = $candidates[$m]['group']; |
8093
|
|
|
if( ($candidate === $defNone) || ($candidate === $defaultResult) ){ |
8094
|
|
|
try{ |
8095
|
|
|
$mixin = $candidates[$m]['mixin']; |
8096
|
|
|
if( !($mixin instanceof Less_Tree_Mixin_Definition) ){ |
8097
|
|
|
$mixin = new Less_Tree_Mixin_Definition('', array(), $mixin->rules, null, false); |
8098
|
|
|
$mixin->originalRuleset = $mixins[$m]->originalRuleset; |
8099
|
|
|
} |
8100
|
|
|
$rules = array_merge($rules, $mixin->evalCall($env, $args, $this->important)->rules); |
8101
|
|
|
} catch (Exception $e) { |
8102
|
|
|
//throw new Less_Exception_Compiler($e->getMessage(), $e->index, null, $this->currentFileInfo['filename']); |
8103
|
|
|
throw new Less_Exception_Compiler($e->getMessage(), null, null, $this->currentFileInfo); |
8104
|
|
|
} |
8105
|
|
|
} |
8106
|
|
|
} |
8107
|
|
|
|
8108
|
|
|
if( $match ){ |
8109
|
|
|
if( !$this->currentFileInfo || !isset($this->currentFileInfo['reference']) || !$this->currentFileInfo['reference'] ){ |
8110
|
|
|
Less_Tree::ReferencedArray($rules); |
8111
|
|
|
} |
8112
|
|
|
|
8113
|
|
|
return $rules; |
8114
|
|
|
} |
8115
|
|
|
} |
8116
|
|
|
|
8117
|
|
|
if( $isOneFound ){ |
8118
|
|
|
throw new Less_Exception_Compiler('No matching definition was found for `'.$this->Format( $args ).'`', null, $this->index, $this->currentFileInfo); |
8119
|
|
|
|
8120
|
|
|
}else{ |
8121
|
|
|
throw new Less_Exception_Compiler(trim($this->selector->toCSS()) . " is undefined in ".$this->currentFileInfo['filename'], null, $this->index); |
8122
|
|
|
} |
8123
|
|
|
|
8124
|
|
|
} |
8125
|
|
|
|
8126
|
|
|
/** |
8127
|
|
|
* Format the args for use in exception messages |
8128
|
|
|
* |
8129
|
|
|
*/ |
8130
|
|
|
private function Format($args){ |
8131
|
|
|
$message = array(); |
8132
|
|
|
if( $args ){ |
8133
|
|
|
foreach($args as $a){ |
8134
|
|
|
$argValue = ''; |
8135
|
|
|
if( $a['name'] ){ |
8136
|
|
|
$argValue += $a['name']+':'; |
8137
|
|
|
} |
8138
|
|
|
if( is_object($a['value']) ){ |
8139
|
|
|
$argValue += $a['value']->toCSS(); |
8140
|
|
|
}else{ |
8141
|
|
|
$argValue += '???'; |
8142
|
|
|
} |
8143
|
|
|
$message[] = $argValue; |
8144
|
|
|
} |
8145
|
|
|
} |
8146
|
|
|
return implode(', ',$message); |
8147
|
|
|
} |
8148
|
|
|
|
8149
|
|
|
|
8150
|
|
|
/** |
8151
|
|
|
* Are we in a recursive mixin call? |
8152
|
|
|
* |
8153
|
|
|
* @return bool |
8154
|
|
|
*/ |
8155
|
|
|
private function IsRecursive( $env, $mixin ){ |
8156
|
|
|
|
8157
|
|
|
foreach($env->frames as $recur_frame){ |
8158
|
|
|
if( !($mixin instanceof Less_Tree_Mixin_Definition) ){ |
8159
|
|
|
|
8160
|
|
|
if( $mixin === $recur_frame ){ |
8161
|
|
|
return true; |
8162
|
|
|
} |
8163
|
|
|
|
8164
|
|
|
if( isset($recur_frame->originalRuleset) && $mixin->ruleset_id === $recur_frame->originalRuleset ){ |
8165
|
|
|
return true; |
8166
|
|
|
} |
8167
|
|
|
} |
8168
|
|
|
} |
8169
|
|
|
|
8170
|
|
|
return false; |
8171
|
|
|
} |
8172
|
|
|
|
8173
|
|
|
} |
8174
|
|
|
|
8175
|
|
|
|
8176
|
|
|
|
8177
|
|
|
|
8178
|
|
|
class Less_Tree_Mixin_Definition extends Less_Tree_Ruleset{ |
8179
|
|
|
public $name; |
8180
|
|
|
public $selectors; |
8181
|
|
|
public $params; |
8182
|
|
|
public $arity = 0; |
8183
|
|
|
public $rules; |
8184
|
|
|
public $lookups = array(); |
8185
|
|
|
public $required = 0; |
8186
|
|
|
public $frames = array(); |
8187
|
|
|
public $condition; |
8188
|
|
|
public $variadic; |
8189
|
|
|
public $type = 'MixinDefinition'; |
8190
|
|
|
|
8191
|
|
|
|
8192
|
|
|
// less.js : /lib/less/tree/mixin.js : tree.mixin.Definition |
8193
|
|
|
public function __construct($name, $params, $rules, $condition, $variadic = false, $frames = array() ){ |
8194
|
|
|
$this->name = $name; |
8195
|
|
|
$this->selectors = array(new Less_Tree_Selector(array( new Less_Tree_Element(null, $name)))); |
8196
|
|
|
|
8197
|
|
|
$this->params = $params; |
8198
|
|
|
$this->condition = $condition; |
8199
|
|
|
$this->variadic = $variadic; |
8200
|
|
|
$this->rules = $rules; |
8201
|
|
|
|
8202
|
|
|
if( $params ){ |
8203
|
|
|
$this->arity = count($params); |
8204
|
|
|
foreach( $params as $p ){ |
8205
|
|
|
if (! isset($p['name']) || ($p['name'] && !isset($p['value']))) { |
8206
|
|
|
$this->required++; |
8207
|
|
|
} |
8208
|
|
|
} |
8209
|
|
|
} |
8210
|
|
|
|
8211
|
|
|
$this->frames = $frames; |
8212
|
|
|
$this->SetRulesetIndex(); |
8213
|
|
|
} |
8214
|
|
|
|
8215
|
|
|
|
8216
|
|
|
|
8217
|
|
|
//function accept( $visitor ){ |
8218
|
|
|
// $this->params = $visitor->visit($this->params); |
8219
|
|
|
// $this->rules = $visitor->visit($this->rules); |
8220
|
|
|
// $this->condition = $visitor->visit($this->condition); |
8221
|
|
|
//} |
8222
|
|
|
|
8223
|
|
|
|
8224
|
|
|
public function toCSS(){ |
8225
|
|
|
return ''; |
8226
|
|
|
} |
8227
|
|
|
|
8228
|
|
|
// less.js : /lib/less/tree/mixin.js : tree.mixin.Definition.evalParams |
8229
|
|
|
public function compileParams($env, $mixinFrames, $args = array() , &$evaldArguments = array() ){ |
8230
|
|
|
$frame = new Less_Tree_Ruleset(null, array()); |
8231
|
|
|
$params = $this->params; |
8232
|
|
|
$mixinEnv = null; |
8233
|
|
|
$argsLength = 0; |
8234
|
|
|
|
8235
|
|
|
if( $args ){ |
|
|
|
|
8236
|
|
|
$argsLength = count($args); |
8237
|
|
|
for($i = 0; $i < $argsLength; $i++ ){ |
8238
|
|
|
$arg = $args[$i]; |
8239
|
|
|
|
8240
|
|
|
if( $arg && $arg['name'] ){ |
8241
|
|
|
$isNamedFound = false; |
8242
|
|
|
|
8243
|
|
|
foreach($params as $j => $param){ |
8244
|
|
|
if( !isset($evaldArguments[$j]) && $arg['name'] === $params[$j]['name']) { |
8245
|
|
|
$evaldArguments[$j] = $arg['value']->compile($env); |
8246
|
|
|
array_unshift($frame->rules, new Less_Tree_Rule( $arg['name'], $arg['value']->compile($env) ) ); |
8247
|
|
|
$isNamedFound = true; |
8248
|
|
|
break; |
8249
|
|
|
} |
8250
|
|
|
} |
8251
|
|
|
if ($isNamedFound) { |
8252
|
|
|
array_splice($args, $i, 1); |
8253
|
|
|
$i--; |
8254
|
|
|
$argsLength--; |
8255
|
|
|
continue; |
8256
|
|
|
} else { |
8257
|
|
|
throw new Less_Exception_Compiler("Named argument for " . $this->name .' '.$args[$i]['name'] . ' not found'); |
8258
|
|
|
} |
8259
|
|
|
} |
8260
|
|
|
} |
8261
|
|
|
} |
8262
|
|
|
|
8263
|
|
|
$argIndex = 0; |
8264
|
|
|
foreach($params as $i => $param){ |
8265
|
|
|
|
8266
|
|
|
if ( isset($evaldArguments[$i]) ){ continue; } |
8267
|
|
|
|
8268
|
|
|
$arg = null; |
8269
|
|
|
if( isset($args[$argIndex]) ){ |
8270
|
|
|
$arg = $args[$argIndex]; |
8271
|
|
|
} |
8272
|
|
|
|
8273
|
|
|
if (isset($param['name']) && $param['name']) { |
8274
|
|
|
|
8275
|
|
|
if( isset($param['variadic']) ){ |
8276
|
|
|
$varargs = array(); |
8277
|
|
View Code Duplication |
for ($j = $argIndex; $j < $argsLength; $j++) { |
8278
|
|
|
$varargs[] = $args[$j]['value']->compile($env); |
8279
|
|
|
} |
8280
|
|
|
$expression = new Less_Tree_Expression($varargs); |
8281
|
|
|
array_unshift($frame->rules, new Less_Tree_Rule($param['name'], $expression->compile($env))); |
8282
|
|
|
}else{ |
8283
|
|
|
$val = ($arg && $arg['value']) ? $arg['value'] : false; |
8284
|
|
|
|
8285
|
|
|
if ($val) { |
8286
|
|
|
$val = $val->compile($env); |
8287
|
|
|
} else if ( isset($param['value']) ) { |
8288
|
|
|
|
8289
|
|
|
if( !$mixinEnv ){ |
8290
|
|
|
$mixinEnv = new Less_Environment(); |
8291
|
|
|
$mixinEnv->frames = array_merge( array($frame), $mixinFrames); |
8292
|
|
|
} |
8293
|
|
|
|
8294
|
|
|
$val = $param['value']->compile($mixinEnv); |
8295
|
|
|
$frame->resetCache(); |
8296
|
|
|
} else { |
8297
|
|
|
throw new Less_Exception_Compiler("Wrong number of arguments for " . $this->name . " (" . $argsLength . ' for ' . $this->arity . ")"); |
8298
|
|
|
} |
8299
|
|
|
|
8300
|
|
|
array_unshift($frame->rules, new Less_Tree_Rule($param['name'], $val)); |
8301
|
|
|
$evaldArguments[$i] = $val; |
8302
|
|
|
} |
8303
|
|
|
} |
8304
|
|
|
|
8305
|
|
|
if ( isset($param['variadic']) && $args) { |
|
|
|
|
8306
|
|
View Code Duplication |
for ($j = $argIndex; $j < $argsLength; $j++) { |
8307
|
|
|
$evaldArguments[$j] = $args[$j]['value']->compile($env); |
8308
|
|
|
} |
8309
|
|
|
} |
8310
|
|
|
$argIndex++; |
8311
|
|
|
} |
8312
|
|
|
|
8313
|
|
|
ksort($evaldArguments); |
8314
|
|
|
$evaldArguments = array_values($evaldArguments); |
8315
|
|
|
|
8316
|
|
|
return $frame; |
8317
|
|
|
} |
8318
|
|
|
|
8319
|
|
|
public function compile($env) { |
8320
|
|
|
if( $this->frames ){ |
|
|
|
|
8321
|
|
|
return new Less_Tree_Mixin_Definition($this->name, $this->params, $this->rules, $this->condition, $this->variadic, $this->frames ); |
8322
|
|
|
} |
8323
|
|
|
return new Less_Tree_Mixin_Definition($this->name, $this->params, $this->rules, $this->condition, $this->variadic, $env->frames ); |
8324
|
|
|
} |
8325
|
|
|
|
8326
|
|
|
public function evalCall($env, $args = NULL, $important = NULL) { |
8327
|
|
|
|
8328
|
|
|
Less_Environment::$mixin_stack++; |
8329
|
|
|
|
8330
|
|
|
$_arguments = array(); |
8331
|
|
|
|
8332
|
|
|
if( $this->frames ){ |
|
|
|
|
8333
|
|
|
$mixinFrames = array_merge($this->frames, $env->frames); |
8334
|
|
|
}else{ |
8335
|
|
|
$mixinFrames = $env->frames; |
8336
|
|
|
} |
8337
|
|
|
|
8338
|
|
|
$frame = $this->compileParams($env, $mixinFrames, $args, $_arguments); |
8339
|
|
|
|
8340
|
|
|
$ex = new Less_Tree_Expression($_arguments); |
8341
|
|
|
array_unshift($frame->rules, new Less_Tree_Rule('@arguments', $ex->compile($env))); |
8342
|
|
|
|
8343
|
|
|
|
8344
|
|
|
$ruleset = new Less_Tree_Ruleset(null, $this->rules); |
8345
|
|
|
$ruleset->originalRuleset = $this->ruleset_id; |
8346
|
|
|
|
8347
|
|
|
|
8348
|
|
|
$ruleSetEnv = new Less_Environment(); |
8349
|
|
|
$ruleSetEnv->frames = array_merge( array($this, $frame), $mixinFrames ); |
8350
|
|
|
$ruleset = $ruleset->compile( $ruleSetEnv ); |
8351
|
|
|
|
8352
|
|
|
if( $important ){ |
8353
|
|
|
$ruleset = $ruleset->makeImportant(); |
8354
|
|
|
} |
8355
|
|
|
|
8356
|
|
|
Less_Environment::$mixin_stack--; |
8357
|
|
|
|
8358
|
|
|
return $ruleset; |
8359
|
|
|
} |
8360
|
|
|
|
8361
|
|
|
|
8362
|
|
|
public function matchCondition($args, $env) { |
8363
|
|
|
|
8364
|
|
|
if( !$this->condition ){ |
8365
|
|
|
return true; |
8366
|
|
|
} |
8367
|
|
|
|
8368
|
|
|
// set array to prevent error on array_merge |
8369
|
|
|
if(!is_array($this->frames)) { |
8370
|
|
|
$this->frames = array(); |
8371
|
|
|
} |
8372
|
|
|
|
8373
|
|
|
$frame = $this->compileParams($env, array_merge($this->frames,$env->frames), $args ); |
8374
|
|
|
|
8375
|
|
|
$compile_env = new Less_Environment(); |
8376
|
|
|
$compile_env->frames = array_merge( |
8377
|
|
|
array($frame) // the parameter variables |
8378
|
|
|
, $this->frames // the parent namespace/mixin frames |
8379
|
|
|
, $env->frames // the current environment frames |
8380
|
|
|
); |
8381
|
|
|
|
8382
|
|
|
$compile_env->functions = $env->functions; |
8383
|
|
|
|
8384
|
|
|
return (bool)$this->condition->compile($compile_env); |
8385
|
|
|
} |
8386
|
|
|
|
8387
|
|
|
public function matchArgs($args, $env = NULL){ |
8388
|
|
|
$argsLength = count($args); |
8389
|
|
|
|
8390
|
|
|
if( !$this->variadic ){ |
8391
|
|
|
if( $argsLength < $this->required ){ |
8392
|
|
|
return false; |
8393
|
|
|
} |
8394
|
|
|
if( $argsLength > count($this->params) ){ |
8395
|
|
|
return false; |
8396
|
|
|
} |
8397
|
|
|
}else{ |
8398
|
|
|
if( $argsLength < ($this->required - 1)){ |
8399
|
|
|
return false; |
8400
|
|
|
} |
8401
|
|
|
} |
8402
|
|
|
|
8403
|
|
|
$len = min($argsLength, $this->arity); |
8404
|
|
|
|
8405
|
|
|
for( $i = 0; $i < $len; $i++ ){ |
8406
|
|
|
if( !isset($this->params[$i]['name']) && !isset($this->params[$i]['variadic']) ){ |
8407
|
|
|
if( $args[$i]['value']->compile($env)->toCSS() != $this->params[$i]['value']->compile($env)->toCSS() ){ |
8408
|
|
|
return false; |
8409
|
|
|
} |
8410
|
|
|
} |
8411
|
|
|
} |
8412
|
|
|
|
8413
|
|
|
return true; |
8414
|
|
|
} |
8415
|
|
|
|
8416
|
|
|
} |
8417
|
|
|
|
8418
|
|
|
|
8419
|
|
|
/** |
8420
|
|
|
* Extend Finder Visitor |
8421
|
|
|
* |
8422
|
|
|
* @package Less |
8423
|
|
|
* @subpackage visitor |
8424
|
|
|
*/ |
8425
|
|
|
class Less_Visitor_extendFinder extends Less_Visitor{ |
8426
|
|
|
|
8427
|
|
|
public $contexts = array(); |
8428
|
|
|
public $allExtendsStack; |
8429
|
|
|
public $foundExtends; |
8430
|
|
|
|
8431
|
|
|
public function __construct(){ |
8432
|
|
|
$this->contexts = array(); |
8433
|
|
|
$this->allExtendsStack = array(array()); |
8434
|
|
|
parent::__construct(); |
8435
|
|
|
} |
8436
|
|
|
|
8437
|
|
|
/** |
8438
|
|
|
* @param Less_Tree_Ruleset $root |
8439
|
|
|
*/ |
8440
|
|
|
public function run($root){ |
8441
|
|
|
$root = $this->visitObj($root); |
8442
|
|
|
$root->allExtends =& $this->allExtendsStack[0]; |
8443
|
|
|
return $root; |
8444
|
|
|
} |
8445
|
|
|
|
8446
|
|
|
public function visitRule($ruleNode, &$visitDeeper ){ |
8447
|
|
|
$visitDeeper = false; |
8448
|
|
|
} |
8449
|
|
|
|
8450
|
|
|
public function visitMixinDefinition( $mixinDefinitionNode, &$visitDeeper ){ |
8451
|
|
|
$visitDeeper = false; |
8452
|
|
|
} |
8453
|
|
|
|
8454
|
|
|
public function visitRuleset($rulesetNode){ |
8455
|
|
|
|
8456
|
|
|
if( $rulesetNode->root ){ |
8457
|
|
|
return; |
8458
|
|
|
} |
8459
|
|
|
|
8460
|
|
|
$allSelectorsExtendList = array(); |
8461
|
|
|
|
8462
|
|
|
// get &:extend(.a); rules which apply to all selectors in this ruleset |
8463
|
|
|
if( $rulesetNode->rules ){ |
8464
|
|
|
foreach($rulesetNode->rules as $rule){ |
8465
|
|
|
if( $rule instanceof Less_Tree_Extend ){ |
8466
|
|
|
$allSelectorsExtendList[] = $rule; |
8467
|
|
|
$rulesetNode->extendOnEveryPath = true; |
8468
|
|
|
} |
8469
|
|
|
} |
8470
|
|
|
} |
8471
|
|
|
|
8472
|
|
|
|
8473
|
|
|
// now find every selector and apply the extends that apply to all extends |
8474
|
|
|
// and the ones which apply to an individual extend |
8475
|
|
|
foreach($rulesetNode->paths as $selectorPath){ |
8476
|
|
|
$selector = end($selectorPath); //$selectorPath[ count($selectorPath)-1]; |
8477
|
|
|
|
8478
|
|
|
$j = 0; |
8479
|
|
|
foreach($selector->extendList as $extend){ |
8480
|
|
|
$this->allExtendsStackPush($rulesetNode, $selectorPath, $extend, $j); |
8481
|
|
|
} |
8482
|
|
|
foreach($allSelectorsExtendList as $extend){ |
8483
|
|
|
$this->allExtendsStackPush($rulesetNode, $selectorPath, $extend, $j); |
8484
|
|
|
} |
8485
|
|
|
} |
8486
|
|
|
|
8487
|
|
|
$this->contexts[] = $rulesetNode->selectors; |
8488
|
|
|
} |
8489
|
|
|
|
8490
|
|
|
public function allExtendsStackPush($rulesetNode, $selectorPath, $extend, &$j){ |
8491
|
|
|
$this->foundExtends = true; |
8492
|
|
|
$extend = clone $extend; |
8493
|
|
|
$extend->findSelfSelectors( $selectorPath ); |
8494
|
|
|
$extend->ruleset = $rulesetNode; |
8495
|
|
|
if( $j === 0 ){ |
8496
|
|
|
$extend->firstExtendOnThisSelectorPath = true; |
8497
|
|
|
} |
8498
|
|
|
|
8499
|
|
|
$end_key = count($this->allExtendsStack)-1; |
8500
|
|
|
$this->allExtendsStack[$end_key][] = $extend; |
8501
|
|
|
$j++; |
8502
|
|
|
} |
8503
|
|
|
|
8504
|
|
|
|
8505
|
|
|
public function visitRulesetOut( $rulesetNode ){ |
8506
|
|
|
if( !is_object($rulesetNode) || !$rulesetNode->root ){ |
8507
|
|
|
array_pop($this->contexts); |
8508
|
|
|
} |
8509
|
|
|
} |
8510
|
|
|
|
8511
|
|
|
public function visitMedia( $mediaNode ){ |
8512
|
|
|
$mediaNode->allExtends = array(); |
8513
|
|
|
$this->allExtendsStack[] =& $mediaNode->allExtends; |
8514
|
|
|
} |
8515
|
|
|
|
8516
|
|
|
public function visitMediaOut(){ |
8517
|
|
|
array_pop($this->allExtendsStack); |
8518
|
|
|
} |
8519
|
|
|
|
8520
|
|
|
public function visitDirective( $directiveNode ){ |
8521
|
|
|
$directiveNode->allExtends = array(); |
8522
|
|
|
$this->allExtendsStack[] =& $directiveNode->allExtends; |
8523
|
|
|
} |
8524
|
|
|
|
8525
|
|
|
public function visitDirectiveOut(){ |
8526
|
|
|
array_pop($this->allExtendsStack); |
8527
|
|
|
} |
8528
|
|
|
} |
8529
|
|
|
|
8530
|
|
|
|
8531
|
|
|
|
8532
|
|
|
|
8533
|
|
|
/* |
8534
|
|
|
class Less_Visitor_import extends Less_VisitorReplacing{ |
8535
|
|
|
|
8536
|
|
|
public $_visitor; |
8537
|
|
|
public $_importer; |
8538
|
|
|
public $importCount; |
8539
|
|
|
|
8540
|
|
|
function __construct( $evalEnv ){ |
8541
|
|
|
$this->env = $evalEnv; |
8542
|
|
|
$this->importCount = 0; |
8543
|
|
|
parent::__construct(); |
8544
|
|
|
} |
8545
|
|
|
|
8546
|
|
|
|
8547
|
|
|
function run( $root ){ |
8548
|
|
|
$root = $this->visitObj($root); |
8549
|
|
|
$this->isFinished = true; |
8550
|
|
|
|
8551
|
|
|
//if( $this->importCount === 0) { |
8552
|
|
|
// $this->_finish(); |
8553
|
|
|
//} |
8554
|
|
|
} |
8555
|
|
|
|
8556
|
|
|
function visitImport($importNode, &$visitDeeper ){ |
8557
|
|
|
$importVisitor = $this; |
8558
|
|
|
$inlineCSS = $importNode->options['inline']; |
8559
|
|
|
|
8560
|
|
|
if( !$importNode->css || $inlineCSS ){ |
8561
|
|
|
$evaldImportNode = $importNode->compileForImport($this->env); |
8562
|
|
|
|
8563
|
|
|
if( $evaldImportNode && (!$evaldImportNode->css || $inlineCSS) ){ |
8564
|
|
|
$importNode = $evaldImportNode; |
8565
|
|
|
$this->importCount++; |
8566
|
|
|
$env = clone $this->env; |
8567
|
|
|
|
8568
|
|
|
if( (isset($importNode->options['multiple']) && $importNode->options['multiple']) ){ |
8569
|
|
|
$env->importMultiple = true; |
8570
|
|
|
} |
8571
|
|
|
|
8572
|
|
|
//get path & uri |
8573
|
|
|
$path_and_uri = null; |
8574
|
|
|
if( is_callable(Less_Parser::$options['import_callback']) ){ |
8575
|
|
|
$path_and_uri = call_user_func(Less_Parser::$options['import_callback'],$importNode); |
8576
|
|
|
} |
8577
|
|
|
|
8578
|
|
|
if( !$path_and_uri ){ |
8579
|
|
|
$path_and_uri = $importNode->PathAndUri(); |
8580
|
|
|
} |
8581
|
|
|
|
8582
|
|
|
if( $path_and_uri ){ |
8583
|
|
|
list($full_path, $uri) = $path_and_uri; |
8584
|
|
|
}else{ |
8585
|
|
|
$full_path = $uri = $importNode->getPath(); |
8586
|
|
|
} |
8587
|
|
|
|
8588
|
|
|
|
8589
|
|
|
//import once |
8590
|
|
|
if( $importNode->skip( $full_path, $env) ){ |
8591
|
|
|
return array(); |
8592
|
|
|
} |
8593
|
|
|
|
8594
|
|
|
if( $importNode->options['inline'] ){ |
8595
|
|
|
//todo needs to reference css file not import |
8596
|
|
|
//$contents = new Less_Tree_Anonymous($importNode->root, 0, array('filename'=>$importNode->importedFilename), true ); |
8597
|
|
|
|
8598
|
|
|
Less_Parser::AddParsedFile($full_path); |
8599
|
|
|
$contents = new Less_Tree_Anonymous( file_get_contents($full_path), 0, array(), true ); |
8600
|
|
|
|
8601
|
|
|
if( $importNode->features ){ |
8602
|
|
|
return new Less_Tree_Media( array($contents), $importNode->features->value ); |
8603
|
|
|
} |
8604
|
|
|
|
8605
|
|
|
return array( $contents ); |
8606
|
|
|
} |
8607
|
|
|
|
8608
|
|
|
|
8609
|
|
|
// css ? |
8610
|
|
|
if( $importNode->css ){ |
8611
|
|
|
$features = ( $importNode->features ? $importNode->features->compile($env) : null ); |
8612
|
|
|
return new Less_Tree_Import( $importNode->compilePath( $env), $features, $importNode->options, $this->index); |
8613
|
|
|
} |
8614
|
|
|
|
8615
|
|
|
return $importNode->ParseImport( $full_path, $uri, $env ); |
8616
|
|
|
} |
8617
|
|
|
|
8618
|
|
|
} |
8619
|
|
|
|
8620
|
|
|
$visitDeeper = false; |
8621
|
|
|
return $importNode; |
8622
|
|
|
} |
8623
|
|
|
|
8624
|
|
|
|
8625
|
|
|
function visitRule( $ruleNode, &$visitDeeper ){ |
8626
|
|
|
$visitDeeper = false; |
8627
|
|
|
return $ruleNode; |
8628
|
|
|
} |
8629
|
|
|
|
8630
|
|
|
function visitDirective($directiveNode, $visitArgs){ |
8631
|
|
|
array_unshift($this->env->frames,$directiveNode); |
8632
|
|
|
return $directiveNode; |
8633
|
|
|
} |
8634
|
|
|
|
8635
|
|
|
function visitDirectiveOut($directiveNode) { |
8636
|
|
|
array_shift($this->env->frames); |
8637
|
|
|
} |
8638
|
|
|
|
8639
|
|
|
function visitMixinDefinition($mixinDefinitionNode, $visitArgs) { |
8640
|
|
|
array_unshift($this->env->frames,$mixinDefinitionNode); |
8641
|
|
|
return $mixinDefinitionNode; |
8642
|
|
|
} |
8643
|
|
|
|
8644
|
|
|
function visitMixinDefinitionOut($mixinDefinitionNode) { |
8645
|
|
|
array_shift($this->env->frames); |
8646
|
|
|
} |
8647
|
|
|
|
8648
|
|
|
function visitRuleset($rulesetNode, $visitArgs) { |
8649
|
|
|
array_unshift($this->env->frames,$rulesetNode); |
8650
|
|
|
return $rulesetNode; |
8651
|
|
|
} |
8652
|
|
|
|
8653
|
|
|
function visitRulesetOut($rulesetNode) { |
8654
|
|
|
array_shift($this->env->frames); |
8655
|
|
|
} |
8656
|
|
|
|
8657
|
|
|
function visitMedia($mediaNode, $visitArgs) { |
8658
|
|
|
array_unshift($this->env->frames, $mediaNode->ruleset); |
8659
|
|
|
return $mediaNode; |
8660
|
|
|
} |
8661
|
|
|
|
8662
|
|
|
function visitMediaOut($mediaNode) { |
8663
|
|
|
array_shift($this->env->frames); |
8664
|
|
|
} |
8665
|
|
|
|
8666
|
|
|
} |
8667
|
|
|
*/ |
8668
|
|
|
|
8669
|
|
|
|
8670
|
|
|
|
8671
|
|
|
|
8672
|
|
|
/** |
8673
|
|
|
* Join Selector Visitor |
8674
|
|
|
* |
8675
|
|
|
* @package Less |
8676
|
|
|
* @subpackage visitor |
8677
|
|
|
*/ |
8678
|
|
|
class Less_Visitor_joinSelector extends Less_Visitor{ |
8679
|
|
|
|
8680
|
|
|
public $contexts = array( array() ); |
8681
|
|
|
|
8682
|
|
|
/** |
8683
|
|
|
* @param Less_Tree_Ruleset $root |
8684
|
|
|
*/ |
8685
|
|
|
public function run( $root ){ |
8686
|
|
|
return $this->visitObj($root); |
8687
|
|
|
} |
8688
|
|
|
|
8689
|
|
|
public function visitRule( $ruleNode, &$visitDeeper ){ |
8690
|
|
|
$visitDeeper = false; |
8691
|
|
|
} |
8692
|
|
|
|
8693
|
|
|
public function visitMixinDefinition( $mixinDefinitionNode, &$visitDeeper ){ |
8694
|
|
|
$visitDeeper = false; |
8695
|
|
|
} |
8696
|
|
|
|
8697
|
|
|
public function visitRuleset( $rulesetNode ){ |
8698
|
|
|
|
8699
|
|
|
$paths = array(); |
8700
|
|
|
|
8701
|
|
|
if( !$rulesetNode->root ){ |
8702
|
|
|
$selectors = array(); |
8703
|
|
|
|
8704
|
|
|
if( $rulesetNode->selectors && $rulesetNode->selectors ){ |
8705
|
|
|
foreach($rulesetNode->selectors as $selector){ |
8706
|
|
|
if( $selector->getIsOutput() ){ |
8707
|
|
|
$selectors[] = $selector; |
8708
|
|
|
} |
8709
|
|
|
} |
8710
|
|
|
} |
8711
|
|
|
|
8712
|
|
|
if( !$selectors ){ |
|
|
|
|
8713
|
|
|
$rulesetNode->selectors = null; |
8714
|
|
|
$rulesetNode->rules = null; |
8715
|
|
|
}else{ |
8716
|
|
|
$context = end($this->contexts); //$context = $this->contexts[ count($this->contexts) - 1]; |
8717
|
|
|
$paths = $rulesetNode->joinSelectors( $context, $selectors); |
8718
|
|
|
} |
8719
|
|
|
|
8720
|
|
|
$rulesetNode->paths = $paths; |
8721
|
|
|
} |
8722
|
|
|
|
8723
|
|
|
$this->contexts[] = $paths; //different from less.js. Placed after joinSelectors() so that $this->contexts will get correct $paths |
8724
|
|
|
} |
8725
|
|
|
|
8726
|
|
|
public function visitRulesetOut(){ |
8727
|
|
|
array_pop($this->contexts); |
8728
|
|
|
} |
8729
|
|
|
|
8730
|
|
|
public function visitMedia($mediaNode) { |
8731
|
|
|
$context = end($this->contexts); //$context = $this->contexts[ count($this->contexts) - 1]; |
8732
|
|
|
|
8733
|
|
|
if( !count($context) || (is_object($context[0]) && $context[0]->multiMedia) ){ |
8734
|
|
|
$mediaNode->rules[0]->root = true; |
8735
|
|
|
} |
8736
|
|
|
} |
8737
|
|
|
|
8738
|
|
|
} |
8739
|
|
|
|
8740
|
|
|
|
8741
|
|
|
|
8742
|
|
|
/** |
8743
|
|
|
* Process Extends Visitor |
8744
|
|
|
* |
8745
|
|
|
* @package Less |
8746
|
|
|
* @subpackage visitor |
8747
|
|
|
*/ |
8748
|
|
|
class Less_Visitor_processExtends extends Less_Visitor{ |
8749
|
|
|
|
8750
|
|
|
public $allExtendsStack; |
8751
|
|
|
|
8752
|
|
|
/** |
8753
|
|
|
* @param Less_Tree_Ruleset $root |
8754
|
|
|
*/ |
8755
|
|
|
public function run( $root ){ |
8756
|
|
|
$extendFinder = new Less_Visitor_extendFinder(); |
8757
|
|
|
$extendFinder->run( $root ); |
8758
|
|
|
if( !$extendFinder->foundExtends){ |
8759
|
|
|
return $root; |
8760
|
|
|
} |
8761
|
|
|
|
8762
|
|
|
$root->allExtends = $this->doExtendChaining( $root->allExtends, $root->allExtends); |
8763
|
|
|
|
8764
|
|
|
$this->allExtendsStack = array(); |
8765
|
|
|
$this->allExtendsStack[] = &$root->allExtends; |
8766
|
|
|
|
8767
|
|
|
return $this->visitObj( $root ); |
8768
|
|
|
} |
8769
|
|
|
|
8770
|
|
|
private function doExtendChaining( $extendsList, $extendsListTarget, $iterationCount = 0){ |
8771
|
|
|
// |
8772
|
|
|
// chaining is different from normal extension.. if we extend an extend then we are not just copying, altering and pasting |
8773
|
|
|
// the selector we would do normally, but we are also adding an extend with the same target selector |
8774
|
|
|
// this means this new extend can then go and alter other extends |
8775
|
|
|
// |
8776
|
|
|
// this method deals with all the chaining work - without it, extend is flat and doesn't work on other extend selectors |
8777
|
|
|
// this is also the most expensive.. and a match on one selector can cause an extension of a selector we had already processed if |
8778
|
|
|
// we look at each selector at a time, as is done in visitRuleset |
8779
|
|
|
|
8780
|
|
|
$extendsToAdd = array(); |
8781
|
|
|
|
8782
|
|
|
|
8783
|
|
|
//loop through comparing every extend with every target extend. |
8784
|
|
|
// a target extend is the one on the ruleset we are looking at copy/edit/pasting in place |
8785
|
|
|
// e.g. .a:extend(.b) {} and .b:extend(.c) {} then the first extend extends the second one |
8786
|
|
|
// and the second is the target. |
8787
|
|
|
// the separation into two lists allows us to process a subset of chains with a bigger set, as is the |
8788
|
|
|
// case when processing media queries |
8789
|
|
|
for( $extendIndex = 0, $extendsList_len = count($extendsList); $extendIndex < $extendsList_len; $extendIndex++ ){ |
8790
|
|
|
for( $targetExtendIndex = 0; $targetExtendIndex < count($extendsListTarget); $targetExtendIndex++ ){ |
|
|
|
|
8791
|
|
|
|
8792
|
|
|
$extend = $extendsList[$extendIndex]; |
8793
|
|
|
$targetExtend = $extendsListTarget[$targetExtendIndex]; |
8794
|
|
|
|
8795
|
|
|
// look for circular references |
8796
|
|
|
if( in_array($targetExtend->object_id, $extend->parent_ids,true) ){ |
8797
|
|
|
continue; |
8798
|
|
|
} |
8799
|
|
|
|
8800
|
|
|
// find a match in the target extends self selector (the bit before :extend) |
8801
|
|
|
$selectorPath = array( $targetExtend->selfSelectors[0] ); |
8802
|
|
|
$matches = $this->findMatch( $extend, $selectorPath); |
8803
|
|
|
|
8804
|
|
|
|
8805
|
|
|
if( $matches ){ |
8806
|
|
|
|
8807
|
|
|
// we found a match, so for each self selector.. |
8808
|
|
|
foreach($extend->selfSelectors as $selfSelector ){ |
8809
|
|
|
|
8810
|
|
|
|
8811
|
|
|
// process the extend as usual |
8812
|
|
|
$newSelector = $this->extendSelector( $matches, $selectorPath, $selfSelector); |
8813
|
|
|
|
8814
|
|
|
// but now we create a new extend from it |
8815
|
|
|
$newExtend = new Less_Tree_Extend( $targetExtend->selector, $targetExtend->option, 0); |
8816
|
|
|
$newExtend->selfSelectors = $newSelector; |
8817
|
|
|
|
8818
|
|
|
// add the extend onto the list of extends for that selector |
8819
|
|
|
end($newSelector)->extendList = array($newExtend); |
8820
|
|
|
//$newSelector[ count($newSelector)-1]->extendList = array($newExtend); |
8821
|
|
|
|
8822
|
|
|
// record that we need to add it. |
8823
|
|
|
$extendsToAdd[] = $newExtend; |
8824
|
|
|
$newExtend->ruleset = $targetExtend->ruleset; |
8825
|
|
|
|
8826
|
|
|
//remember its parents for circular references |
8827
|
|
|
$newExtend->parent_ids = array_merge($newExtend->parent_ids,$targetExtend->parent_ids,$extend->parent_ids); |
8828
|
|
|
|
8829
|
|
|
// only process the selector once.. if we have :extend(.a,.b) then multiple |
8830
|
|
|
// extends will look at the same selector path, so when extending |
8831
|
|
|
// we know that any others will be duplicates in terms of what is added to the css |
8832
|
|
|
if( $targetExtend->firstExtendOnThisSelectorPath ){ |
8833
|
|
|
$newExtend->firstExtendOnThisSelectorPath = true; |
8834
|
|
|
$targetExtend->ruleset->paths[] = $newSelector; |
8835
|
|
|
} |
8836
|
|
|
} |
8837
|
|
|
} |
8838
|
|
|
} |
8839
|
|
|
} |
8840
|
|
|
|
8841
|
|
|
if( $extendsToAdd ){ |
|
|
|
|
8842
|
|
|
// try to detect circular references to stop a stack overflow. |
8843
|
|
|
// may no longer be needed. $this->extendChainCount++; |
8844
|
|
|
if( $iterationCount > 100) { |
8845
|
|
|
|
8846
|
|
|
try{ |
8847
|
|
|
$selectorOne = $extendsToAdd[0]->selfSelectors[0]->toCSS(); |
8848
|
|
|
$selectorTwo = $extendsToAdd[0]->selector->toCSS(); |
8849
|
|
|
}catch(Exception $e){ |
8850
|
|
|
$selectorOne = "{unable to calculate}"; |
8851
|
|
|
$selectorTwo = "{unable to calculate}"; |
8852
|
|
|
} |
8853
|
|
|
|
8854
|
|
|
throw new Less_Exception_Parser("extend circular reference detected. One of the circular extends is currently:"+$selectorOne+":extend(" + $selectorTwo+")"); |
8855
|
|
|
} |
8856
|
|
|
|
8857
|
|
|
// now process the new extends on the existing rules so that we can handle a extending b extending c ectending d extending e... |
8858
|
|
|
$extendsToAdd = $this->doExtendChaining( $extendsToAdd, $extendsListTarget, $iterationCount+1); |
8859
|
|
|
} |
8860
|
|
|
|
8861
|
|
|
return array_merge($extendsList, $extendsToAdd); |
8862
|
|
|
} |
8863
|
|
|
|
8864
|
|
|
|
8865
|
|
|
protected function visitRule( $ruleNode, &$visitDeeper ){ |
8866
|
|
|
$visitDeeper = false; |
8867
|
|
|
} |
8868
|
|
|
|
8869
|
|
|
protected function visitMixinDefinition( $mixinDefinitionNode, &$visitDeeper ){ |
8870
|
|
|
$visitDeeper = false; |
8871
|
|
|
} |
8872
|
|
|
|
8873
|
|
|
protected function visitSelector( $selectorNode, &$visitDeeper ){ |
8874
|
|
|
$visitDeeper = false; |
8875
|
|
|
} |
8876
|
|
|
|
8877
|
|
|
protected function visitRuleset($rulesetNode){ |
8878
|
|
|
|
8879
|
|
|
|
8880
|
|
|
if( $rulesetNode->root ){ |
8881
|
|
|
return; |
8882
|
|
|
} |
8883
|
|
|
|
8884
|
|
|
$allExtends = end($this->allExtendsStack); |
8885
|
|
|
$paths_len = count($rulesetNode->paths); |
8886
|
|
|
|
8887
|
|
|
// look at each selector path in the ruleset, find any extend matches and then copy, find and replace |
8888
|
|
|
foreach($allExtends as $allExtend){ |
8889
|
|
|
for($pathIndex = 0; $pathIndex < $paths_len; $pathIndex++ ){ |
8890
|
|
|
|
8891
|
|
|
// extending extends happens initially, before the main pass |
8892
|
|
|
if( isset($rulesetNode->extendOnEveryPath) && $rulesetNode->extendOnEveryPath ){ |
8893
|
|
|
continue; |
8894
|
|
|
} |
8895
|
|
|
|
8896
|
|
|
$selectorPath = $rulesetNode->paths[$pathIndex]; |
8897
|
|
|
|
8898
|
|
|
if( end($selectorPath)->extendList ){ |
8899
|
|
|
continue; |
8900
|
|
|
} |
8901
|
|
|
|
8902
|
|
|
$this->ExtendMatch( $rulesetNode, $allExtend, $selectorPath); |
8903
|
|
|
|
8904
|
|
|
} |
8905
|
|
|
} |
8906
|
|
|
} |
8907
|
|
|
|
8908
|
|
|
|
8909
|
|
|
private function ExtendMatch( $rulesetNode, $extend, $selectorPath ){ |
8910
|
|
|
$matches = $this->findMatch($extend, $selectorPath); |
8911
|
|
|
|
8912
|
|
|
if( $matches ){ |
8913
|
|
|
foreach($extend->selfSelectors as $selfSelector ){ |
8914
|
|
|
$rulesetNode->paths[] = $this->extendSelector($matches, $selectorPath, $selfSelector); |
8915
|
|
|
} |
8916
|
|
|
} |
8917
|
|
|
} |
8918
|
|
|
|
8919
|
|
|
|
8920
|
|
|
|
8921
|
|
|
private function findMatch($extend, $haystackSelectorPath ){ |
8922
|
|
|
|
8923
|
|
|
|
8924
|
|
|
if( !$this->HasMatches($extend, $haystackSelectorPath) ){ |
8925
|
|
|
return false; |
8926
|
|
|
} |
8927
|
|
|
|
8928
|
|
|
|
8929
|
|
|
// |
8930
|
|
|
// look through the haystack selector path to try and find the needle - extend.selector |
8931
|
|
|
// returns an array of selector matches that can then be replaced |
8932
|
|
|
// |
8933
|
|
|
$needleElements = $extend->selector->elements; |
8934
|
|
|
$potentialMatches = array(); |
8935
|
|
|
$potentialMatches_len = 0; |
8936
|
|
|
$potentialMatch = null; |
8937
|
|
|
$matches = array(); |
8938
|
|
|
|
8939
|
|
|
|
8940
|
|
|
|
8941
|
|
|
// loop through the haystack elements |
8942
|
|
|
$haystack_path_len = count($haystackSelectorPath); |
8943
|
|
|
for($haystackSelectorIndex = 0; $haystackSelectorIndex < $haystack_path_len; $haystackSelectorIndex++ ){ |
8944
|
|
|
$hackstackSelector = $haystackSelectorPath[$haystackSelectorIndex]; |
8945
|
|
|
|
8946
|
|
|
$haystack_elements_len = count($hackstackSelector->elements); |
8947
|
|
|
for($hackstackElementIndex = 0; $hackstackElementIndex < $haystack_elements_len; $hackstackElementIndex++ ){ |
8948
|
|
|
|
8949
|
|
|
$haystackElement = $hackstackSelector->elements[$hackstackElementIndex]; |
8950
|
|
|
|
8951
|
|
|
// if we allow elements before our match we can add a potential match every time. otherwise only at the first element. |
8952
|
|
|
if( $extend->allowBefore || ($haystackSelectorIndex === 0 && $hackstackElementIndex === 0) ){ |
8953
|
|
|
$potentialMatches[] = array('pathIndex'=> $haystackSelectorIndex, 'index'=> $hackstackElementIndex, 'matched'=> 0, 'initialCombinator'=> $haystackElement->combinator); |
8954
|
|
|
$potentialMatches_len++; |
8955
|
|
|
} |
8956
|
|
|
|
8957
|
|
|
for($i = 0; $i < $potentialMatches_len; $i++ ){ |
8958
|
|
|
|
8959
|
|
|
$potentialMatch = &$potentialMatches[$i]; |
8960
|
|
|
$potentialMatch = $this->PotentialMatch( $potentialMatch, $needleElements, $haystackElement, $hackstackElementIndex ); |
8961
|
|
|
|
8962
|
|
|
|
8963
|
|
|
// if we are still valid and have finished, test whether we have elements after and whether these are allowed |
8964
|
|
|
if( $potentialMatch && $potentialMatch['matched'] === $extend->selector->elements_len ){ |
|
|
|
|
8965
|
|
|
$potentialMatch['finished'] = true; |
8966
|
|
|
|
8967
|
|
|
if( !$extend->allowAfter && ($hackstackElementIndex+1 < $haystack_elements_len || $haystackSelectorIndex+1 < $haystack_path_len) ){ |
8968
|
|
|
$potentialMatch = null; |
8969
|
|
|
} |
8970
|
|
|
} |
8971
|
|
|
|
8972
|
|
|
// if null we remove, if not, we are still valid, so either push as a valid match or continue |
8973
|
|
|
if( $potentialMatch ){ |
8974
|
|
|
if( $potentialMatch['finished'] ){ |
8975
|
|
|
$potentialMatch['length'] = $extend->selector->elements_len; |
8976
|
|
|
$potentialMatch['endPathIndex'] = $haystackSelectorIndex; |
8977
|
|
|
$potentialMatch['endPathElementIndex'] = $hackstackElementIndex + 1; // index after end of match |
8978
|
|
|
$potentialMatches = array(); // we don't allow matches to overlap, so start matching again |
8979
|
|
|
$potentialMatches_len = 0; |
8980
|
|
|
$matches[] = $potentialMatch; |
8981
|
|
|
} |
8982
|
|
|
continue; |
8983
|
|
|
} |
8984
|
|
|
|
8985
|
|
|
array_splice($potentialMatches, $i, 1); |
8986
|
|
|
$potentialMatches_len--; |
8987
|
|
|
$i--; |
8988
|
|
|
} |
8989
|
|
|
} |
8990
|
|
|
} |
8991
|
|
|
|
8992
|
|
|
return $matches; |
8993
|
|
|
} |
8994
|
|
|
|
8995
|
|
|
|
8996
|
|
|
// Before going through all the nested loops, lets check to see if a match is possible |
8997
|
|
|
// Reduces Bootstrap 3.1 compile time from ~6.5s to ~5.6s |
8998
|
|
|
private function HasMatches($extend, $haystackSelectorPath){ |
8999
|
|
|
|
9000
|
|
|
if( !$extend->selector->cacheable ){ |
9001
|
|
|
return true; |
9002
|
|
|
} |
9003
|
|
|
|
9004
|
|
|
$first_el = $extend->selector->_oelements[0]; |
9005
|
|
|
|
9006
|
|
|
foreach($haystackSelectorPath as $hackstackSelector){ |
9007
|
|
|
if( !$hackstackSelector->cacheable ){ |
9008
|
|
|
return true; |
9009
|
|
|
} |
9010
|
|
|
|
9011
|
|
|
if( in_array($first_el, $hackstackSelector->_oelements) ){ |
9012
|
|
|
return true; |
9013
|
|
|
} |
9014
|
|
|
} |
9015
|
|
|
|
9016
|
|
|
return false; |
9017
|
|
|
} |
9018
|
|
|
|
9019
|
|
|
|
9020
|
|
|
/** |
9021
|
|
|
* @param integer $hackstackElementIndex |
9022
|
|
|
*/ |
9023
|
|
|
private function PotentialMatch( $potentialMatch, $needleElements, $haystackElement, $hackstackElementIndex ){ |
9024
|
|
|
|
9025
|
|
|
|
9026
|
|
|
if( $potentialMatch['matched'] > 0 ){ |
9027
|
|
|
|
9028
|
|
|
// selectors add " " onto the first element. When we use & it joins the selectors together, but if we don't |
9029
|
|
|
// then each selector in haystackSelectorPath has a space before it added in the toCSS phase. so we need to work out |
9030
|
|
|
// what the resulting combinator will be |
9031
|
|
|
$targetCombinator = $haystackElement->combinator; |
9032
|
|
|
if( $targetCombinator === '' && $hackstackElementIndex === 0 ){ |
9033
|
|
|
$targetCombinator = ' '; |
9034
|
|
|
} |
9035
|
|
|
|
9036
|
|
|
if( $needleElements[ $potentialMatch['matched'] ]->combinator !== $targetCombinator ){ |
9037
|
|
|
return null; |
9038
|
|
|
} |
9039
|
|
|
} |
9040
|
|
|
|
9041
|
|
|
// if we don't match, null our match to indicate failure |
9042
|
|
|
if( !$this->isElementValuesEqual( $needleElements[$potentialMatch['matched'] ]->value, $haystackElement->value) ){ |
9043
|
|
|
return null; |
9044
|
|
|
} |
9045
|
|
|
|
9046
|
|
|
$potentialMatch['finished'] = false; |
9047
|
|
|
$potentialMatch['matched']++; |
9048
|
|
|
|
9049
|
|
|
return $potentialMatch; |
9050
|
|
|
} |
9051
|
|
|
|
9052
|
|
|
|
9053
|
|
|
private function isElementValuesEqual( $elementValue1, $elementValue2 ){ |
9054
|
|
|
|
9055
|
|
|
if( $elementValue1 === $elementValue2 ){ |
9056
|
|
|
return true; |
9057
|
|
|
} |
9058
|
|
|
|
9059
|
|
|
if( is_string($elementValue1) || is_string($elementValue2) ) { |
9060
|
|
|
return false; |
9061
|
|
|
} |
9062
|
|
|
|
9063
|
|
|
if( $elementValue1 instanceof Less_Tree_Attribute ){ |
9064
|
|
|
return $this->isAttributeValuesEqual( $elementValue1, $elementValue2 ); |
9065
|
|
|
} |
9066
|
|
|
|
9067
|
|
|
$elementValue1 = $elementValue1->value; |
9068
|
|
|
if( $elementValue1 instanceof Less_Tree_Selector ){ |
9069
|
|
|
return $this->isSelectorValuesEqual( $elementValue1, $elementValue2 ); |
9070
|
|
|
} |
9071
|
|
|
|
9072
|
|
|
return false; |
9073
|
|
|
} |
9074
|
|
|
|
9075
|
|
|
|
9076
|
|
|
/** |
9077
|
|
|
* @param Less_Tree_Selector $elementValue1 |
9078
|
|
|
*/ |
9079
|
|
|
private function isSelectorValuesEqual( $elementValue1, $elementValue2 ){ |
9080
|
|
|
|
9081
|
|
|
$elementValue2 = $elementValue2->value; |
9082
|
|
|
if( !($elementValue2 instanceof Less_Tree_Selector) || $elementValue1->elements_len !== $elementValue2->elements_len ){ |
9083
|
|
|
return false; |
9084
|
|
|
} |
9085
|
|
|
|
9086
|
|
|
for( $i = 0; $i < $elementValue1->elements_len; $i++ ){ |
9087
|
|
|
|
9088
|
|
|
if( $elementValue1->elements[$i]->combinator !== $elementValue2->elements[$i]->combinator ){ |
9089
|
|
|
if( $i !== 0 || ($elementValue1->elements[$i]->combinator || ' ') !== ($elementValue2->elements[$i]->combinator || ' ') ){ |
9090
|
|
|
return false; |
9091
|
|
|
} |
9092
|
|
|
} |
9093
|
|
|
|
9094
|
|
|
if( !$this->isElementValuesEqual($elementValue1->elements[$i]->value, $elementValue2->elements[$i]->value) ){ |
9095
|
|
|
return false; |
9096
|
|
|
} |
9097
|
|
|
} |
9098
|
|
|
|
9099
|
|
|
return true; |
9100
|
|
|
} |
9101
|
|
|
|
9102
|
|
|
|
9103
|
|
|
/** |
9104
|
|
|
* @param Less_Tree_Attribute $elementValue1 |
9105
|
|
|
*/ |
9106
|
|
|
private function isAttributeValuesEqual( $elementValue1, $elementValue2 ){ |
9107
|
|
|
|
9108
|
|
|
if( $elementValue1->op !== $elementValue2->op || $elementValue1->key !== $elementValue2->key ){ |
9109
|
|
|
return false; |
9110
|
|
|
} |
9111
|
|
|
|
9112
|
|
|
if( !$elementValue1->value || !$elementValue2->value ){ |
9113
|
|
|
if( $elementValue1->value || $elementValue2->value ) { |
9114
|
|
|
return false; |
9115
|
|
|
} |
9116
|
|
|
return true; |
9117
|
|
|
} |
9118
|
|
|
|
9119
|
|
|
$elementValue1 = ($elementValue1->value->value ? $elementValue1->value->value : $elementValue1->value ); |
9120
|
|
|
$elementValue2 = ($elementValue2->value->value ? $elementValue2->value->value : $elementValue2->value ); |
9121
|
|
|
|
9122
|
|
|
return $elementValue1 === $elementValue2; |
9123
|
|
|
} |
9124
|
|
|
|
9125
|
|
|
|
9126
|
|
|
private function extendSelector($matches, $selectorPath, $replacementSelector){ |
9127
|
|
|
|
9128
|
|
|
//for a set of matches, replace each match with the replacement selector |
9129
|
|
|
|
9130
|
|
|
$currentSelectorPathIndex = 0; |
9131
|
|
|
$currentSelectorPathElementIndex = 0; |
9132
|
|
|
$path = array(); |
9133
|
|
|
$selectorPath_len = count($selectorPath); |
9134
|
|
|
|
9135
|
|
|
for($matchIndex = 0, $matches_len = count($matches); $matchIndex < $matches_len; $matchIndex++ ){ |
9136
|
|
|
|
9137
|
|
|
|
9138
|
|
|
$match = $matches[$matchIndex]; |
9139
|
|
|
$selector = $selectorPath[ $match['pathIndex'] ]; |
9140
|
|
|
|
9141
|
|
|
$firstElement = new Less_Tree_Element( |
9142
|
|
|
$match['initialCombinator'], |
9143
|
|
|
$replacementSelector->elements[0]->value, |
9144
|
|
|
$replacementSelector->elements[0]->index, |
9145
|
|
|
$replacementSelector->elements[0]->currentFileInfo |
9146
|
|
|
); |
9147
|
|
|
|
9148
|
|
View Code Duplication |
if( $match['pathIndex'] > $currentSelectorPathIndex && $currentSelectorPathElementIndex > 0 ){ |
9149
|
|
|
$last_path = end($path); |
9150
|
|
|
$last_path->elements = array_merge( $last_path->elements, array_slice( $selectorPath[$currentSelectorPathIndex]->elements, $currentSelectorPathElementIndex)); |
9151
|
|
|
$currentSelectorPathElementIndex = 0; |
9152
|
|
|
$currentSelectorPathIndex++; |
9153
|
|
|
} |
9154
|
|
|
|
9155
|
|
|
$newElements = array_merge( |
9156
|
|
|
array_slice($selector->elements, $currentSelectorPathElementIndex, ($match['index'] - $currentSelectorPathElementIndex) ) // last parameter of array_slice is different than the last parameter of javascript's slice |
9157
|
|
|
, array($firstElement) |
9158
|
|
|
, array_slice($replacementSelector->elements,1) |
9159
|
|
|
); |
9160
|
|
|
|
9161
|
|
|
if( $currentSelectorPathIndex === $match['pathIndex'] && $matchIndex > 0 ){ |
9162
|
|
|
$last_key = count($path)-1; |
9163
|
|
|
$path[$last_key]->elements = array_merge($path[$last_key]->elements,$newElements); |
9164
|
|
|
}else{ |
9165
|
|
|
$path = array_merge( $path, array_slice( $selectorPath, $currentSelectorPathIndex, $match['pathIndex'] )); |
9166
|
|
|
$path[] = new Less_Tree_Selector( $newElements ); |
9167
|
|
|
} |
9168
|
|
|
|
9169
|
|
|
$currentSelectorPathIndex = $match['endPathIndex']; |
9170
|
|
|
$currentSelectorPathElementIndex = $match['endPathElementIndex']; |
9171
|
|
|
if( $currentSelectorPathElementIndex >= count($selectorPath[$currentSelectorPathIndex]->elements) ){ |
9172
|
|
|
$currentSelectorPathElementIndex = 0; |
9173
|
|
|
$currentSelectorPathIndex++; |
9174
|
|
|
} |
9175
|
|
|
} |
9176
|
|
|
|
9177
|
|
View Code Duplication |
if( $currentSelectorPathIndex < $selectorPath_len && $currentSelectorPathElementIndex > 0 ){ |
9178
|
|
|
$last_path = end($path); |
9179
|
|
|
$last_path->elements = array_merge( $last_path->elements, array_slice($selectorPath[$currentSelectorPathIndex]->elements, $currentSelectorPathElementIndex)); |
9180
|
|
|
$currentSelectorPathIndex++; |
9181
|
|
|
} |
9182
|
|
|
|
9183
|
|
|
$slice_len = $selectorPath_len - $currentSelectorPathIndex; |
9184
|
|
|
$path = array_merge($path, array_slice($selectorPath, $currentSelectorPathIndex, $slice_len)); |
9185
|
|
|
|
9186
|
|
|
return $path; |
9187
|
|
|
} |
9188
|
|
|
|
9189
|
|
|
|
9190
|
|
|
protected function visitMedia( $mediaNode ){ |
9191
|
|
|
$newAllExtends = array_merge( $mediaNode->allExtends, end($this->allExtendsStack) ); |
9192
|
|
|
$this->allExtendsStack[] = $this->doExtendChaining($newAllExtends, $mediaNode->allExtends); |
9193
|
|
|
} |
9194
|
|
|
|
9195
|
|
|
protected function visitMediaOut(){ |
9196
|
|
|
array_pop( $this->allExtendsStack ); |
9197
|
|
|
} |
9198
|
|
|
|
9199
|
|
|
protected function visitDirective( $directiveNode ){ |
9200
|
|
|
$newAllExtends = array_merge( $directiveNode->allExtends, end($this->allExtendsStack) ); |
9201
|
|
|
$this->allExtendsStack[] = $this->doExtendChaining($newAllExtends, $directiveNode->allExtends); |
9202
|
|
|
} |
9203
|
|
|
|
9204
|
|
|
protected function visitDirectiveOut(){ |
9205
|
|
|
array_pop($this->allExtendsStack); |
9206
|
|
|
} |
9207
|
|
|
|
9208
|
|
|
} |
9209
|
|
|
|
9210
|
|
|
/** |
9211
|
|
|
* toCSS Visitor |
9212
|
|
|
* |
9213
|
|
|
* @package Less |
9214
|
|
|
* @subpackage visitor |
9215
|
|
|
*/ |
9216
|
|
|
class Less_Visitor_toCSS extends Less_VisitorReplacing{ |
9217
|
|
|
|
9218
|
|
|
private $charset; |
9219
|
|
|
|
9220
|
|
|
public function __construct(){ |
9221
|
|
|
parent::__construct(); |
9222
|
|
|
} |
9223
|
|
|
|
9224
|
|
|
/** |
9225
|
|
|
* @param Less_Tree_Ruleset $root |
9226
|
|
|
*/ |
9227
|
|
|
public function run( $root ){ |
9228
|
|
|
return $this->visitObj($root); |
9229
|
|
|
} |
9230
|
|
|
|
9231
|
|
|
public function visitRule( $ruleNode ){ |
9232
|
|
|
if( $ruleNode->variable ){ |
9233
|
|
|
return array(); |
9234
|
|
|
} |
9235
|
|
|
return $ruleNode; |
9236
|
|
|
} |
9237
|
|
|
|
9238
|
|
|
public function visitMixinDefinition($mixinNode){ |
9239
|
|
|
// mixin definitions do not get eval'd - this means they keep state |
9240
|
|
|
// so we have to clear that state here so it isn't used if toCSS is called twice |
9241
|
|
|
$mixinNode->frames = array(); |
9242
|
|
|
return array(); |
9243
|
|
|
} |
9244
|
|
|
|
9245
|
|
|
public function visitExtend(){ |
9246
|
|
|
return array(); |
9247
|
|
|
} |
9248
|
|
|
|
9249
|
|
|
public function visitComment( $commentNode ){ |
9250
|
|
|
if( $commentNode->isSilent() ){ |
9251
|
|
|
return array(); |
9252
|
|
|
} |
9253
|
|
|
return $commentNode; |
9254
|
|
|
} |
9255
|
|
|
|
9256
|
|
|
public function visitMedia( $mediaNode, &$visitDeeper ){ |
9257
|
|
|
$mediaNode->accept($this); |
9258
|
|
|
$visitDeeper = false; |
9259
|
|
|
|
9260
|
|
|
if( !$mediaNode->rules ){ |
9261
|
|
|
return array(); |
9262
|
|
|
} |
9263
|
|
|
return $mediaNode; |
9264
|
|
|
} |
9265
|
|
|
|
9266
|
|
|
public function visitDirective( $directiveNode ){ |
9267
|
|
|
if( isset($directiveNode->currentFileInfo['reference']) && (!property_exists($directiveNode,'isReferenced') || !$directiveNode->isReferenced) ){ |
9268
|
|
|
return array(); |
9269
|
|
|
} |
9270
|
|
|
if( $directiveNode->name === '@charset' ){ |
9271
|
|
|
// Only output the debug info together with subsequent @charset definitions |
9272
|
|
|
// a comment (or @media statement) before the actual @charset directive would |
9273
|
|
|
// be considered illegal css as it has to be on the first line |
9274
|
|
|
if( isset($this->charset) && $this->charset ){ |
9275
|
|
|
|
9276
|
|
|
//if( $directiveNode->debugInfo ){ |
9277
|
|
|
// $comment = new Less_Tree_Comment('/* ' . str_replace("\n",'',$directiveNode->toCSS())." */\n"); |
9278
|
|
|
// $comment->debugInfo = $directiveNode->debugInfo; |
9279
|
|
|
// return $this->visit($comment); |
9280
|
|
|
//} |
9281
|
|
|
|
9282
|
|
|
|
9283
|
|
|
return array(); |
9284
|
|
|
} |
9285
|
|
|
$this->charset = true; |
9286
|
|
|
} |
9287
|
|
|
return $directiveNode; |
9288
|
|
|
} |
9289
|
|
|
|
9290
|
|
|
public function checkPropertiesInRoot( $rulesetNode ){ |
9291
|
|
|
|
9292
|
|
|
if( !$rulesetNode->firstRoot ){ |
9293
|
|
|
return; |
9294
|
|
|
} |
9295
|
|
|
|
9296
|
|
|
foreach($rulesetNode->rules as $ruleNode){ |
9297
|
|
|
if( $ruleNode instanceof Less_Tree_Rule && !$ruleNode->variable ){ |
9298
|
|
|
$msg = "properties must be inside selector blocks, they cannot be in the root. Index ".$ruleNode->index.($ruleNode->currentFileInfo ? (' Filename: '.$ruleNode->currentFileInfo['filename']) : null); |
9299
|
|
|
throw new Less_Exception_Compiler($msg); |
9300
|
|
|
} |
9301
|
|
|
} |
9302
|
|
|
} |
9303
|
|
|
|
9304
|
|
|
|
9305
|
|
|
public function visitRuleset( $rulesetNode, &$visitDeeper ){ |
9306
|
|
|
|
9307
|
|
|
$visitDeeper = false; |
9308
|
|
|
|
9309
|
|
|
$this->checkPropertiesInRoot( $rulesetNode ); |
9310
|
|
|
|
9311
|
|
|
if( $rulesetNode->root ){ |
9312
|
|
|
return $this->visitRulesetRoot( $rulesetNode ); |
9313
|
|
|
} |
9314
|
|
|
|
9315
|
|
|
$rulesets = array(); |
9316
|
|
|
$rulesetNode->paths = $this->visitRulesetPaths($rulesetNode); |
9317
|
|
|
|
9318
|
|
|
|
9319
|
|
|
// Compile rules and rulesets |
9320
|
|
|
$nodeRuleCnt = count($rulesetNode->rules); |
9321
|
|
|
for( $i = 0; $i < $nodeRuleCnt; ){ |
9322
|
|
|
$rule = $rulesetNode->rules[$i]; |
9323
|
|
|
|
9324
|
|
|
if( property_exists($rule,'rules') ){ |
9325
|
|
|
// visit because we are moving them out from being a child |
9326
|
|
|
$rulesets[] = $this->visitObj($rule); |
9327
|
|
|
array_splice($rulesetNode->rules,$i,1); |
9328
|
|
|
$nodeRuleCnt--; |
9329
|
|
|
continue; |
9330
|
|
|
} |
9331
|
|
|
$i++; |
9332
|
|
|
} |
9333
|
|
|
|
9334
|
|
|
|
9335
|
|
|
// accept the visitor to remove rules and refactor itself |
9336
|
|
|
// then we can decide now whether we want it or not |
9337
|
|
|
if( $nodeRuleCnt > 0 ){ |
9338
|
|
|
$rulesetNode->accept($this); |
9339
|
|
|
|
9340
|
|
|
if( $rulesetNode->rules ){ |
|
|
|
|
9341
|
|
|
|
9342
|
|
|
if( count($rulesetNode->rules) > 1 ){ |
9343
|
|
|
$this->_mergeRules( $rulesetNode->rules ); |
9344
|
|
|
$this->_removeDuplicateRules( $rulesetNode->rules ); |
9345
|
|
|
} |
9346
|
|
|
|
9347
|
|
|
// now decide whether we keep the ruleset |
9348
|
|
|
if( $rulesetNode->paths ){ |
|
|
|
|
9349
|
|
|
//array_unshift($rulesets, $rulesetNode); |
9350
|
|
|
array_splice($rulesets,0,0,array($rulesetNode)); |
9351
|
|
|
} |
9352
|
|
|
} |
9353
|
|
|
|
9354
|
|
|
} |
9355
|
|
|
|
9356
|
|
|
|
9357
|
|
|
if( count($rulesets) === 1 ){ |
9358
|
|
|
return $rulesets[0]; |
9359
|
|
|
} |
9360
|
|
|
return $rulesets; |
9361
|
|
|
} |
9362
|
|
|
|
9363
|
|
|
|
9364
|
|
|
/** |
9365
|
|
|
* Helper function for visitiRuleset |
9366
|
|
|
* |
9367
|
|
|
* return array|Less_Tree_Ruleset |
9368
|
|
|
*/ |
9369
|
|
|
private function visitRulesetRoot( $rulesetNode ){ |
9370
|
|
|
$rulesetNode->accept( $this ); |
9371
|
|
|
if( $rulesetNode->firstRoot || $rulesetNode->rules ){ |
9372
|
|
|
return $rulesetNode; |
9373
|
|
|
} |
9374
|
|
|
return array(); |
9375
|
|
|
} |
9376
|
|
|
|
9377
|
|
|
|
9378
|
|
|
/** |
9379
|
|
|
* Helper function for visitRuleset() |
9380
|
|
|
* |
9381
|
|
|
* @return array |
9382
|
|
|
*/ |
9383
|
|
|
private function visitRulesetPaths($rulesetNode){ |
9384
|
|
|
|
9385
|
|
|
$paths = array(); |
9386
|
|
|
foreach($rulesetNode->paths as $p){ |
9387
|
|
|
if( $p[0]->elements[0]->combinator === ' ' ){ |
9388
|
|
|
$p[0]->elements[0]->combinator = ''; |
9389
|
|
|
} |
9390
|
|
|
|
9391
|
|
|
foreach($p as $pi){ |
9392
|
|
|
if( $pi->getIsReferenced() && $pi->getIsOutput() ){ |
9393
|
|
|
$paths[] = $p; |
9394
|
|
|
break; |
9395
|
|
|
} |
9396
|
|
|
} |
9397
|
|
|
} |
9398
|
|
|
|
9399
|
|
|
return $paths; |
9400
|
|
|
} |
9401
|
|
|
|
9402
|
|
|
protected function _removeDuplicateRules( &$rules ){ |
9403
|
|
|
// remove duplicates |
9404
|
|
|
$ruleCache = array(); |
9405
|
|
|
for( $i = count($rules)-1; $i >= 0 ; $i-- ){ |
9406
|
|
|
$rule = $rules[$i]; |
9407
|
|
|
if( $rule instanceof Less_Tree_Rule || $rule instanceof Less_Tree_NameValue ){ |
9408
|
|
|
|
9409
|
|
|
if( !isset($ruleCache[$rule->name]) ){ |
9410
|
|
|
$ruleCache[$rule->name] = $rule; |
9411
|
|
|
}else{ |
9412
|
|
|
$ruleList =& $ruleCache[$rule->name]; |
9413
|
|
|
|
9414
|
|
|
if( $ruleList instanceof Less_Tree_Rule || $ruleList instanceof Less_Tree_NameValue ){ |
9415
|
|
|
$ruleList = $ruleCache[$rule->name] = array( $ruleCache[$rule->name]->toCSS() ); |
9416
|
|
|
} |
9417
|
|
|
|
9418
|
|
|
$ruleCSS = $rule->toCSS(); |
9419
|
|
|
if( array_search($ruleCSS,$ruleList) !== false ){ |
9420
|
|
|
array_splice($rules,$i,1); |
9421
|
|
|
}else{ |
9422
|
|
|
$ruleList[] = $ruleCSS; |
9423
|
|
|
} |
9424
|
|
|
} |
9425
|
|
|
} |
9426
|
|
|
} |
9427
|
|
|
} |
9428
|
|
|
|
9429
|
|
|
protected function _mergeRules( &$rules ){ |
9430
|
|
|
$groups = array(); |
9431
|
|
|
|
9432
|
|
|
//obj($rules); |
9433
|
|
|
|
9434
|
|
|
$rules_len = count($rules); |
9435
|
|
|
for( $i = 0; $i < $rules_len; $i++ ){ |
9436
|
|
|
$rule = $rules[$i]; |
9437
|
|
|
|
9438
|
|
|
if( ($rule instanceof Less_Tree_Rule) && $rule->merge ){ |
9439
|
|
|
|
9440
|
|
|
$key = $rule->name; |
9441
|
|
|
if( $rule->important ){ |
9442
|
|
|
$key .= ',!'; |
9443
|
|
|
} |
9444
|
|
|
|
9445
|
|
|
if( !isset($groups[$key]) ){ |
9446
|
|
|
$groups[$key] = array(); |
9447
|
|
|
}else{ |
9448
|
|
|
array_splice($rules, $i--, 1); |
9449
|
|
|
$rules_len--; |
9450
|
|
|
} |
9451
|
|
|
|
9452
|
|
|
$groups[$key][] = $rule; |
9453
|
|
|
} |
9454
|
|
|
} |
9455
|
|
|
|
9456
|
|
|
|
9457
|
|
|
foreach($groups as $parts){ |
9458
|
|
|
|
9459
|
|
|
if( count($parts) > 1 ){ |
9460
|
|
|
$rule = $parts[0]; |
9461
|
|
|
$spacedGroups = array(); |
9462
|
|
|
$lastSpacedGroup = array(); |
9463
|
|
|
$parts_mapped = array(); |
9464
|
|
|
foreach($parts as $p){ |
9465
|
|
|
if( $p->merge === '+' ){ |
9466
|
|
|
if( $lastSpacedGroup ){ |
|
|
|
|
9467
|
|
|
$spacedGroups[] = self::toExpression($lastSpacedGroup); |
9468
|
|
|
} |
9469
|
|
|
$lastSpacedGroup = array(); |
9470
|
|
|
} |
9471
|
|
|
$lastSpacedGroup[] = $p; |
9472
|
|
|
} |
9473
|
|
|
|
9474
|
|
|
$spacedGroups[] = self::toExpression($lastSpacedGroup); |
9475
|
|
|
$rule->value = self::toValue($spacedGroups); |
9476
|
|
|
} |
9477
|
|
|
} |
9478
|
|
|
|
9479
|
|
|
} |
9480
|
|
|
|
9481
|
|
|
public static function toExpression($values){ |
9482
|
|
|
$mapped = array(); |
9483
|
|
|
foreach($values as $p){ |
9484
|
|
|
$mapped[] = $p->value; |
9485
|
|
|
} |
9486
|
|
|
return new Less_Tree_Expression( $mapped ); |
9487
|
|
|
} |
9488
|
|
|
|
9489
|
|
|
public static function toValue($values){ |
9490
|
|
|
//return new Less_Tree_Value($values); ?? |
9491
|
|
|
|
9492
|
|
|
$mapped = array(); |
9493
|
|
|
foreach($values as $p){ |
9494
|
|
|
$mapped[] = $p; |
9495
|
|
|
} |
9496
|
|
|
return new Less_Tree_Value($mapped); |
9497
|
|
|
} |
9498
|
|
|
} |
9499
|
|
|
|
9500
|
|
|
|
9501
|
|
|
|
9502
|
|
|
/** |
9503
|
|
|
* Parser Exception |
9504
|
|
|
* |
9505
|
|
|
* @package Less |
9506
|
|
|
* @subpackage exception |
9507
|
|
|
*/ |
9508
|
|
|
class Less_Exception_Parser extends Exception{ |
9509
|
|
|
|
9510
|
|
|
/** |
9511
|
|
|
* The current file |
9512
|
|
|
* |
9513
|
|
|
* @var Less_ImportedFile |
9514
|
|
|
*/ |
9515
|
|
|
public $currentFile; |
9516
|
|
|
|
9517
|
|
|
/** |
9518
|
|
|
* The current parser index |
9519
|
|
|
* |
9520
|
|
|
* @var integer |
9521
|
|
|
*/ |
9522
|
|
|
public $index; |
9523
|
|
|
|
9524
|
|
|
protected $input; |
9525
|
|
|
|
9526
|
|
|
protected $details = array(); |
9527
|
|
|
|
9528
|
|
|
|
9529
|
|
|
/** |
9530
|
|
|
* Constructor |
9531
|
|
|
* |
9532
|
|
|
* @param string $message |
9533
|
|
|
* @param Exception $previous Previous exception |
9534
|
|
|
* @param integer $index The current parser index |
9535
|
|
|
* @param Less_FileInfo|string $currentFile The file |
9536
|
|
|
* @param integer $code The exception code |
9537
|
|
|
*/ |
9538
|
|
|
public function __construct($message = null, Exception $previous = null, $index = null, $currentFile = null, $code = 0){ |
9539
|
|
|
|
9540
|
|
|
if (PHP_VERSION_ID < 50300) { |
9541
|
|
|
$this->previous = $previous; |
9542
|
|
|
parent::__construct($message, $code); |
9543
|
|
|
} else { |
9544
|
|
|
parent::__construct($message, $code, $previous); |
9545
|
|
|
} |
9546
|
|
|
|
9547
|
|
|
$this->currentFile = $currentFile; |
9548
|
|
|
$this->index = $index; |
9549
|
|
|
|
9550
|
|
|
$this->genMessage(); |
9551
|
|
|
} |
9552
|
|
|
|
9553
|
|
|
|
9554
|
|
|
protected function getInput(){ |
9555
|
|
|
|
9556
|
|
|
if( !$this->input && $this->currentFile && $this->currentFile['filename'] && file_exists($this->currentFile['filename']) ){ |
9557
|
|
|
$this->input = file_get_contents( $this->currentFile['filename'] ); |
9558
|
|
|
} |
9559
|
|
|
} |
9560
|
|
|
|
9561
|
|
|
|
9562
|
|
|
|
9563
|
|
|
/** |
9564
|
|
|
* Converts the exception to string |
9565
|
|
|
* |
9566
|
|
|
* @return string |
9567
|
|
|
*/ |
9568
|
|
|
public function genMessage(){ |
9569
|
|
|
|
9570
|
|
|
if( $this->currentFile && $this->currentFile['filename'] ){ |
9571
|
|
|
$this->message .= ' in '.basename($this->currentFile['filename']); |
9572
|
|
|
} |
9573
|
|
|
|
9574
|
|
|
if( $this->index !== null ){ |
9575
|
|
|
$this->getInput(); |
9576
|
|
|
if( $this->input ){ |
9577
|
|
|
$line = self::getLineNumber(); |
9578
|
|
|
$this->message .= ' on line '.$line.', column '.self::getColumn(); |
9579
|
|
|
|
9580
|
|
|
$lines = explode("\n",$this->input); |
9581
|
|
|
|
9582
|
|
|
$count = count($lines); |
9583
|
|
|
$start_line = max(0, $line-3); |
9584
|
|
|
$last_line = min($count, $start_line+6); |
9585
|
|
|
$num_len = strlen($last_line); |
9586
|
|
|
for( $i = $start_line; $i < $last_line; $i++ ){ |
9587
|
|
|
$this->message .= "\n".str_pad($i+1,$num_len,'0',STR_PAD_LEFT).'| '.$lines[$i]; |
9588
|
|
|
} |
9589
|
|
|
} |
9590
|
|
|
} |
9591
|
|
|
|
9592
|
|
|
} |
9593
|
|
|
|
9594
|
|
|
/** |
9595
|
|
|
* Returns the line number the error was encountered |
9596
|
|
|
* |
9597
|
|
|
* @return integer |
9598
|
|
|
*/ |
9599
|
|
|
public function getLineNumber(){ |
9600
|
|
|
if( $this->index ){ |
9601
|
|
|
// https://bugs.php.net/bug.php?id=49790 |
9602
|
|
|
if (ini_get("mbstring.func_overload")) { |
9603
|
|
|
return substr_count(substr($this->input, 0, $this->index), "\n") + 1; |
9604
|
|
|
} else { |
9605
|
|
|
return substr_count($this->input, "\n", 0, $this->index) + 1; |
9606
|
|
|
} |
9607
|
|
|
} |
9608
|
|
|
return 1; |
9609
|
|
|
} |
9610
|
|
|
|
9611
|
|
|
|
9612
|
|
|
/** |
9613
|
|
|
* Returns the column the error was encountered |
9614
|
|
|
* |
9615
|
|
|
* @return integer |
9616
|
|
|
*/ |
9617
|
|
|
public function getColumn(){ |
9618
|
|
|
|
9619
|
|
|
$part = substr($this->input, 0, $this->index); |
9620
|
|
|
$pos = strrpos($part,"\n"); |
9621
|
|
|
return $this->index - $pos; |
9622
|
|
|
} |
9623
|
|
|
|
9624
|
|
|
} |
9625
|
|
|
|
9626
|
|
|
|
9627
|
|
|
/** |
9628
|
|
|
* Chunk Exception |
9629
|
|
|
* |
9630
|
|
|
* @package Less |
9631
|
|
|
* @subpackage exception |
9632
|
|
|
*/ |
9633
|
|
|
class Less_Exception_Chunk extends Less_Exception_Parser{ |
9634
|
|
|
|
9635
|
|
|
|
9636
|
|
|
protected $parserCurrentIndex = 0; |
9637
|
|
|
|
9638
|
|
|
protected $emitFrom = 0; |
9639
|
|
|
|
9640
|
|
|
protected $input_len; |
9641
|
|
|
|
9642
|
|
|
|
9643
|
|
|
/** |
9644
|
|
|
* Constructor |
9645
|
|
|
* |
9646
|
|
|
* @param string $input |
9647
|
|
|
* @param Exception $previous Previous exception |
9648
|
|
|
* @param integer $index The current parser index |
9649
|
|
|
* @param Less_FileInfo|string $currentFile The file |
9650
|
|
|
* @param integer $code The exception code |
9651
|
|
|
*/ |
9652
|
|
|
public function __construct($input, Exception $previous = null, $index = null, $currentFile = null, $code = 0){ |
9653
|
|
|
|
9654
|
|
|
$this->message = 'ParseError: Unexpected input'; //default message |
9655
|
|
|
|
9656
|
|
|
$this->index = $index; |
9657
|
|
|
|
9658
|
|
|
$this->currentFile = $currentFile; |
9659
|
|
|
|
9660
|
|
|
$this->input = $input; |
9661
|
|
|
$this->input_len = strlen($input); |
9662
|
|
|
|
9663
|
|
|
$this->Chunks(); |
9664
|
|
|
$this->genMessage(); |
9665
|
|
|
} |
9666
|
|
|
|
9667
|
|
|
|
9668
|
|
|
/** |
9669
|
|
|
* See less.js chunks() |
9670
|
|
|
* We don't actually need the chunks |
9671
|
|
|
* |
9672
|
|
|
*/ |
9673
|
|
|
protected function Chunks(){ |
9674
|
|
|
$level = 0; |
9675
|
|
|
$parenLevel = 0; |
9676
|
|
|
$lastMultiCommentEndBrace = null; |
9677
|
|
|
$lastOpening = null; |
9678
|
|
|
$lastMultiComment = null; |
9679
|
|
|
$lastParen = null; |
9680
|
|
|
|
9681
|
|
|
for( $this->parserCurrentIndex = 0; $this->parserCurrentIndex < $this->input_len; $this->parserCurrentIndex++ ){ |
9682
|
|
|
$cc = $this->CharCode($this->parserCurrentIndex); |
9683
|
|
|
if ((($cc >= 97) && ($cc <= 122)) || ($cc < 34)) { |
9684
|
|
|
// a-z or whitespace |
9685
|
|
|
continue; |
9686
|
|
|
} |
9687
|
|
|
|
9688
|
|
|
switch ($cc) { |
9689
|
|
|
|
9690
|
|
|
// ( |
9691
|
|
|
case 40: |
9692
|
|
|
$parenLevel++; |
9693
|
|
|
$lastParen = $this->parserCurrentIndex; |
9694
|
|
|
continue; |
9695
|
|
|
|
9696
|
|
|
// ) |
9697
|
|
|
case 41: |
9698
|
|
|
$parenLevel--; |
9699
|
|
|
if( $parenLevel < 0 ){ |
9700
|
|
|
return $this->fail("missing opening `(`"); |
9701
|
|
|
} |
9702
|
|
|
continue; |
9703
|
|
|
|
9704
|
|
|
// ; |
9705
|
|
|
case 59: |
9706
|
|
|
//if (!$parenLevel) { $this->emitChunk(); } |
9707
|
|
|
continue; |
9708
|
|
|
|
9709
|
|
|
// { |
9710
|
|
|
case 123: |
9711
|
|
|
$level++; |
9712
|
|
|
$lastOpening = $this->parserCurrentIndex; |
9713
|
|
|
continue; |
9714
|
|
|
|
9715
|
|
|
// } |
9716
|
|
|
case 125: |
9717
|
|
|
$level--; |
9718
|
|
|
if( $level < 0 ){ |
9719
|
|
|
return $this->fail("missing opening `{`"); |
9720
|
|
|
|
9721
|
|
|
} |
9722
|
|
|
//if (!$level && !$parenLevel) { $this->emitChunk(); } |
9723
|
|
|
continue; |
9724
|
|
|
// \ |
9725
|
|
|
case 92: |
9726
|
|
|
if ($this->parserCurrentIndex < $this->input_len - 1) { $this->parserCurrentIndex++; continue; } |
9727
|
|
|
return $this->fail("unescaped `\\`"); |
9728
|
|
|
|
9729
|
|
|
// ", ' and ` |
9730
|
|
|
case 34: |
9731
|
|
|
case 39: |
9732
|
|
|
case 96: |
9733
|
|
|
$matched = 0; |
9734
|
|
|
$currentChunkStartIndex = $this->parserCurrentIndex; |
9735
|
|
|
for ($this->parserCurrentIndex = $this->parserCurrentIndex + 1; $this->parserCurrentIndex < $this->input_len; $this->parserCurrentIndex++) { |
9736
|
|
|
$cc2 = $this->CharCode($this->parserCurrentIndex); |
9737
|
|
|
if ($cc2 > 96) { continue; } |
9738
|
|
|
if ($cc2 == $cc) { $matched = 1; break; } |
9739
|
|
|
if ($cc2 == 92) { // \ |
9740
|
|
|
if ($this->parserCurrentIndex == $this->input_len - 1) { |
9741
|
|
|
return $this->fail("unescaped `\\`"); |
9742
|
|
|
} |
9743
|
|
|
$this->parserCurrentIndex++; |
9744
|
|
|
} |
9745
|
|
|
} |
9746
|
|
|
if ($matched) { continue; } |
9747
|
|
|
return $this->fail("unmatched `" + chr($cc) + "`", $currentChunkStartIndex); |
9748
|
|
|
|
9749
|
|
|
// /, check for comment |
9750
|
|
|
case 47: |
9751
|
|
|
if ($parenLevel || ($this->parserCurrentIndex == $this->input_len - 1)) { continue; } |
9752
|
|
|
$cc2 = $this->CharCode($this->parserCurrentIndex+1); |
9753
|
|
|
if ($cc2 == 47) { |
9754
|
|
|
// //, find lnfeed |
9755
|
|
|
for ($this->parserCurrentIndex = $this->parserCurrentIndex + 2; $this->parserCurrentIndex < $this->input_len; $this->parserCurrentIndex++) { |
9756
|
|
|
$cc2 = $this->CharCode($this->parserCurrentIndex); |
9757
|
|
|
if (($cc2 <= 13) && (($cc2 == 10) || ($cc2 == 13))) { break; } |
9758
|
|
|
} |
9759
|
|
|
} else if ($cc2 == 42) { |
9760
|
|
|
// /*, find */ |
9761
|
|
|
$lastMultiComment = $currentChunkStartIndex = $this->parserCurrentIndex; |
9762
|
|
|
for ($this->parserCurrentIndex = $this->parserCurrentIndex + 2; $this->parserCurrentIndex < $this->input_len - 1; $this->parserCurrentIndex++) { |
9763
|
|
|
$cc2 = $this->CharCode($this->parserCurrentIndex); |
9764
|
|
|
if ($cc2 == 125) { $lastMultiCommentEndBrace = $this->parserCurrentIndex; } |
9765
|
|
|
if ($cc2 != 42) { continue; } |
9766
|
|
|
if ($this->CharCode($this->parserCurrentIndex+1) == 47) { break; } |
9767
|
|
|
} |
9768
|
|
|
if ($this->parserCurrentIndex == $this->input_len - 1) { |
9769
|
|
|
return $this->fail("missing closing `*/`", $currentChunkStartIndex); |
9770
|
|
|
} |
9771
|
|
|
} |
9772
|
|
|
continue; |
9773
|
|
|
|
9774
|
|
|
// *, check for unmatched */ |
9775
|
|
|
case 42: |
9776
|
|
|
if (($this->parserCurrentIndex < $this->input_len - 1) && ($this->CharCode($this->parserCurrentIndex+1) == 47)) { |
9777
|
|
|
return $this->fail("unmatched `/*`"); |
9778
|
|
|
} |
9779
|
|
|
continue; |
9780
|
|
|
} |
9781
|
|
|
} |
9782
|
|
|
|
9783
|
|
|
if( $level !== 0 ){ |
9784
|
|
|
if( ($lastMultiComment > $lastOpening) && ($lastMultiCommentEndBrace > $lastMultiComment) ){ |
9785
|
|
|
return $this->fail("missing closing `}` or `*/`", $lastOpening); |
9786
|
|
|
} else { |
9787
|
|
|
return $this->fail("missing closing `}`", $lastOpening); |
9788
|
|
|
} |
9789
|
|
|
} else if ( $parenLevel !== 0 ){ |
9790
|
|
|
return $this->fail("missing closing `)`", $lastParen); |
9791
|
|
|
} |
9792
|
|
|
|
9793
|
|
|
|
9794
|
|
|
//chunk didn't fail |
9795
|
|
|
|
9796
|
|
|
|
9797
|
|
|
//$this->emitChunk(true); |
9798
|
|
|
} |
9799
|
|
|
|
9800
|
|
|
public function CharCode($pos){ |
9801
|
|
|
return ord($this->input[$pos]); |
9802
|
|
|
} |
9803
|
|
|
|
9804
|
|
|
|
9805
|
|
|
public function fail( $msg, $index = null ){ |
9806
|
|
|
|
9807
|
|
|
if( !$index ){ |
9808
|
|
|
$this->index = $this->parserCurrentIndex; |
9809
|
|
|
}else{ |
9810
|
|
|
$this->index = $index; |
9811
|
|
|
} |
9812
|
|
|
$this->message = 'ParseError: '.$msg; |
9813
|
|
|
} |
9814
|
|
|
|
9815
|
|
|
|
9816
|
|
|
/* |
9817
|
|
|
function emitChunk( $force = false ){ |
9818
|
|
|
$len = $this->parserCurrentIndex - $this->emitFrom; |
9819
|
|
|
if ((($len < 512) && !$force) || !$len) { |
9820
|
|
|
return; |
9821
|
|
|
} |
9822
|
|
|
$chunks[] = substr($this->input, $this->emitFrom, $this->parserCurrentIndex + 1 - $this->emitFrom ); |
9823
|
|
|
$this->emitFrom = $this->parserCurrentIndex + 1; |
9824
|
|
|
} |
9825
|
|
|
*/ |
9826
|
|
|
|
9827
|
|
|
} |
9828
|
|
|
|
9829
|
|
|
|
9830
|
|
|
/** |
9831
|
|
|
* Compiler Exception |
9832
|
|
|
* |
9833
|
|
|
* @package Less |
9834
|
|
|
* @subpackage exception |
9835
|
|
|
*/ |
9836
|
|
|
class Less_Exception_Compiler extends Less_Exception_Parser{ |
9837
|
|
|
|
9838
|
|
|
} |
9839
|
|
|
|
9840
|
|
|
/** |
9841
|
|
|
* Parser output with source map |
9842
|
|
|
* |
9843
|
|
|
* @package Less |
9844
|
|
|
* @subpackage Output |
9845
|
|
|
*/ |
9846
|
|
|
class Less_Output_Mapped extends Less_Output { |
9847
|
|
|
|
9848
|
|
|
/** |
9849
|
|
|
* The source map generator |
9850
|
|
|
* |
9851
|
|
|
* @var Less_SourceMap_Generator |
9852
|
|
|
*/ |
9853
|
|
|
protected $generator; |
9854
|
|
|
|
9855
|
|
|
/** |
9856
|
|
|
* Current line |
9857
|
|
|
* |
9858
|
|
|
* @var integer |
9859
|
|
|
*/ |
9860
|
|
|
protected $lineNumber = 0; |
9861
|
|
|
|
9862
|
|
|
/** |
9863
|
|
|
* Current column |
9864
|
|
|
* |
9865
|
|
|
* @var integer |
9866
|
|
|
*/ |
9867
|
|
|
protected $column = 0; |
9868
|
|
|
|
9869
|
|
|
/** |
9870
|
|
|
* Array of contents map (file and its content) |
9871
|
|
|
* |
9872
|
|
|
* @var array |
9873
|
|
|
*/ |
9874
|
|
|
protected $contentsMap = array(); |
9875
|
|
|
|
9876
|
|
|
/** |
9877
|
|
|
* Constructor |
9878
|
|
|
* |
9879
|
|
|
* @param array $contentsMap Array of filename to contents map |
9880
|
|
|
* @param Less_SourceMap_Generator $generator |
9881
|
|
|
*/ |
9882
|
|
|
public function __construct(array $contentsMap, $generator){ |
9883
|
|
|
$this->contentsMap = $contentsMap; |
9884
|
|
|
$this->generator = $generator; |
9885
|
|
|
} |
9886
|
|
|
|
9887
|
|
|
/** |
9888
|
|
|
* Adds a chunk to the stack |
9889
|
|
|
* The $index for less.php may be different from less.js since less.php does not chunkify inputs |
9890
|
|
|
* |
9891
|
|
|
* @param string $chunk |
9892
|
|
|
* @param string $fileInfo |
9893
|
|
|
* @param integer $index |
9894
|
|
|
* @param mixed $mapLines |
9895
|
|
|
*/ |
9896
|
|
|
public function add($chunk, $fileInfo = null, $index = 0, $mapLines = null){ |
9897
|
|
|
|
9898
|
|
|
//ignore adding empty strings |
9899
|
|
|
if( $chunk === '' ){ |
9900
|
|
|
return; |
9901
|
|
|
} |
9902
|
|
|
|
9903
|
|
|
|
9904
|
|
|
$sourceLines = array(); |
9905
|
|
|
$sourceColumns = ' '; |
9906
|
|
|
|
9907
|
|
|
|
9908
|
|
|
if( $fileInfo ){ |
|
|
|
|
9909
|
|
|
|
9910
|
|
|
$url = $fileInfo['currentUri']; |
9911
|
|
|
|
9912
|
|
|
if( isset($this->contentsMap[$url]) ){ |
9913
|
|
|
$inputSource = substr($this->contentsMap[$url], 0, $index); |
9914
|
|
|
$sourceLines = explode("\n", $inputSource); |
9915
|
|
|
$sourceColumns = end($sourceLines); |
9916
|
|
|
}else{ |
9917
|
|
|
throw new Exception('Filename '.$url.' not in contentsMap'); |
9918
|
|
|
} |
9919
|
|
|
|
9920
|
|
|
} |
9921
|
|
|
|
9922
|
|
|
$lines = explode("\n", $chunk); |
9923
|
|
|
$columns = end($lines); |
9924
|
|
|
|
9925
|
|
|
if($fileInfo){ |
|
|
|
|
9926
|
|
|
|
9927
|
|
|
if(!$mapLines){ |
9928
|
|
|
$this->generator->addMapping( |
9929
|
|
|
$this->lineNumber + 1, // generated_line |
9930
|
|
|
$this->column, // generated_column |
9931
|
|
|
count($sourceLines), // original_line |
9932
|
|
|
strlen($sourceColumns), // original_column |
9933
|
|
|
$fileInfo |
9934
|
|
|
); |
9935
|
|
|
}else{ |
9936
|
|
|
for($i = 0, $count = count($lines); $i < $count; $i++){ |
9937
|
|
|
$this->generator->addMapping( |
9938
|
|
|
$this->lineNumber + $i + 1, // generated_line |
9939
|
|
|
$i === 0 ? $this->column : 0, // generated_column |
9940
|
|
|
count($sourceLines) + $i, // original_line |
9941
|
|
|
$i === 0 ? strlen($sourceColumns) : 0, // original_column |
9942
|
|
|
$fileInfo |
9943
|
|
|
); |
9944
|
|
|
} |
9945
|
|
|
} |
9946
|
|
|
} |
9947
|
|
|
|
9948
|
|
|
if(count($lines) === 1){ |
9949
|
|
|
$this->column += strlen($columns); |
9950
|
|
|
}else{ |
9951
|
|
|
$this->lineNumber += count($lines) - 1; |
9952
|
|
|
$this->column = strlen($columns); |
9953
|
|
|
} |
9954
|
|
|
|
9955
|
|
|
// add only chunk |
9956
|
|
|
parent::add($chunk); |
9957
|
|
|
} |
9958
|
|
|
|
9959
|
|
|
} |
9960
|
|
|
|
9961
|
|
|
/** |
9962
|
|
|
* Encode / Decode Base64 VLQ. |
9963
|
|
|
* |
9964
|
|
|
* @package Less |
9965
|
|
|
* @subpackage SourceMap |
9966
|
|
|
*/ |
9967
|
|
|
class Less_SourceMap_Base64VLQ { |
9968
|
|
|
|
9969
|
|
|
/** |
9970
|
|
|
* Shift |
9971
|
|
|
* |
9972
|
|
|
* @var integer |
9973
|
|
|
*/ |
9974
|
|
|
private $shift = 5; |
9975
|
|
|
|
9976
|
|
|
/** |
9977
|
|
|
* Mask |
9978
|
|
|
* |
9979
|
|
|
* @var integer |
9980
|
|
|
*/ |
9981
|
|
|
private $mask = 0x1F; // == (1 << shift) == 0b00011111 |
9982
|
|
|
|
9983
|
|
|
/** |
9984
|
|
|
* Continuation bit |
9985
|
|
|
* |
9986
|
|
|
* @var integer |
9987
|
|
|
*/ |
9988
|
|
|
private $continuationBit = 0x20; // == (mask - 1 ) == 0b00100000 |
9989
|
|
|
|
9990
|
|
|
/** |
9991
|
|
|
* Char to integer map |
9992
|
|
|
* |
9993
|
|
|
* @var array |
9994
|
|
|
*/ |
9995
|
|
|
private $charToIntMap = array( |
9996
|
|
|
'A' => 0, 'B' => 1, 'C' => 2, 'D' => 3, 'E' => 4, 'F' => 5, 'G' => 6, |
9997
|
|
|
'H' => 7,'I' => 8, 'J' => 9, 'K' => 10, 'L' => 11, 'M' => 12, 'N' => 13, |
9998
|
|
|
'O' => 14, 'P' => 15, 'Q' => 16, 'R' => 17, 'S' => 18, 'T' => 19, 'U' => 20, |
9999
|
|
|
'V' => 21, 'W' => 22, 'X' => 23, 'Y' => 24, 'Z' => 25, 'a' => 26, 'b' => 27, |
10000
|
|
|
'c' => 28, 'd' => 29, 'e' => 30, 'f' => 31, 'g' => 32, 'h' => 33, 'i' => 34, |
10001
|
|
|
'j' => 35, 'k' => 36, 'l' => 37, 'm' => 38, 'n' => 39, 'o' => 40, 'p' => 41, |
10002
|
|
|
'q' => 42, 'r' => 43, 's' => 44, 't' => 45, 'u' => 46, 'v' => 47, 'w' => 48, |
10003
|
|
|
'x' => 49, 'y' => 50, 'z' => 51, 0 => 52, 1 => 53, 2 => 54, 3 => 55, 4 => 56, |
10004
|
|
|
5 => 57, 6 => 58, 7 => 59, 8 => 60, 9 => 61, '+' => 62, '/' => 63, |
10005
|
|
|
); |
10006
|
|
|
|
10007
|
|
|
/** |
10008
|
|
|
* Integer to char map |
10009
|
|
|
* |
10010
|
|
|
* @var array |
10011
|
|
|
*/ |
10012
|
|
|
private $intToCharMap = array( |
10013
|
|
|
0 => 'A', 1 => 'B', 2 => 'C', 3 => 'D', 4 => 'E', 5 => 'F', 6 => 'G', |
10014
|
|
|
7 => 'H', 8 => 'I', 9 => 'J', 10 => 'K', 11 => 'L', 12 => 'M', 13 => 'N', |
10015
|
|
|
14 => 'O', 15 => 'P', 16 => 'Q', 17 => 'R', 18 => 'S', 19 => 'T', 20 => 'U', |
10016
|
|
|
21 => 'V', 22 => 'W', 23 => 'X', 24 => 'Y', 25 => 'Z', 26 => 'a', 27 => 'b', |
10017
|
|
|
28 => 'c', 29 => 'd', 30 => 'e', 31 => 'f', 32 => 'g', 33 => 'h', 34 => 'i', |
10018
|
|
|
35 => 'j', 36 => 'k', 37 => 'l', 38 => 'm', 39 => 'n', 40 => 'o', 41 => 'p', |
10019
|
|
|
42 => 'q', 43 => 'r', 44 => 's', 45 => 't', 46 => 'u', 47 => 'v', 48 => 'w', |
10020
|
|
|
49 => 'x', 50 => 'y', 51 => 'z', 52 => '0', 53 => '1', 54 => '2', 55 => '3', |
10021
|
|
|
56 => '4', 57 => '5', 58 => '6', 59 => '7', 60 => '8', 61 => '9', 62 => '+', |
10022
|
|
|
63 => '/', |
10023
|
|
|
); |
10024
|
|
|
|
10025
|
|
|
/** |
10026
|
|
|
* Constructor |
10027
|
|
|
*/ |
10028
|
|
|
public function __construct(){ |
10029
|
|
|
// I leave it here for future reference |
10030
|
|
|
// foreach(str_split('ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/') as $i => $char) |
10031
|
|
|
// { |
10032
|
|
|
// $this->charToIntMap[$char] = $i; |
10033
|
|
|
// $this->intToCharMap[$i] = $char; |
10034
|
|
|
// } |
10035
|
|
|
} |
10036
|
|
|
|
10037
|
|
|
/** |
10038
|
|
|
* Convert from a two-complement value to a value where the sign bit is |
10039
|
|
|
* is placed in the least significant bit. For example, as decimals: |
10040
|
|
|
* 1 becomes 2 (10 binary), -1 becomes 3 (11 binary) |
10041
|
|
|
* 2 becomes 4 (100 binary), -2 becomes 5 (101 binary) |
10042
|
|
|
* We generate the value for 32 bit machines, hence -2147483648 becomes 1, not 4294967297, |
10043
|
|
|
* even on a 64 bit machine. |
10044
|
|
|
* @param string $aValue |
10045
|
|
|
*/ |
10046
|
|
|
public function toVLQSigned($aValue){ |
10047
|
|
|
return 0xffffffff & ($aValue < 0 ? ((-$aValue) << 1) + 1 : ($aValue << 1) + 0); |
10048
|
|
|
} |
10049
|
|
|
|
10050
|
|
|
/** |
10051
|
|
|
* Convert to a two-complement value from a value where the sign bit is |
10052
|
|
|
* is placed in the least significant bit. For example, as decimals: |
10053
|
|
|
* 2 (10 binary) becomes 1, 3 (11 binary) becomes -1 |
10054
|
|
|
* 4 (100 binary) becomes 2, 5 (101 binary) becomes -2 |
10055
|
|
|
* We assume that the value was generated with a 32 bit machine in mind. |
10056
|
|
|
* Hence |
10057
|
|
|
* 1 becomes -2147483648 |
10058
|
|
|
* even on a 64 bit machine. |
10059
|
|
|
* @param integer $aValue |
10060
|
|
|
*/ |
10061
|
|
|
public function fromVLQSigned($aValue){ |
10062
|
|
|
return $aValue & 1 ? $this->zeroFill(~$aValue + 2, 1) | (-1 - 0x7fffffff) : $this->zeroFill($aValue, 1); |
10063
|
|
|
} |
10064
|
|
|
|
10065
|
|
|
/** |
10066
|
|
|
* Return the base 64 VLQ encoded value. |
10067
|
|
|
* |
10068
|
|
|
* @param string $aValue The value to encode |
10069
|
|
|
* @return string The encoded value |
10070
|
|
|
*/ |
10071
|
|
|
public function encode($aValue){ |
10072
|
|
|
$encoded = ''; |
10073
|
|
|
$vlq = $this->toVLQSigned($aValue); |
10074
|
|
|
do |
10075
|
|
|
{ |
10076
|
|
|
$digit = $vlq & $this->mask; |
10077
|
|
|
$vlq = $this->zeroFill($vlq, $this->shift); |
10078
|
|
|
if($vlq > 0){ |
10079
|
|
|
$digit |= $this->continuationBit; |
10080
|
|
|
} |
10081
|
|
|
$encoded .= $this->base64Encode($digit); |
10082
|
|
|
} while($vlq > 0); |
10083
|
|
|
|
10084
|
|
|
return $encoded; |
10085
|
|
|
} |
10086
|
|
|
|
10087
|
|
|
/** |
10088
|
|
|
* Return the value decoded from base 64 VLQ. |
10089
|
|
|
* |
10090
|
|
|
* @param string $encoded The encoded value to decode |
10091
|
|
|
* @return integer The decoded value |
10092
|
|
|
*/ |
10093
|
|
|
public function decode($encoded){ |
10094
|
|
|
$vlq = 0; |
10095
|
|
|
$i = 0; |
10096
|
|
|
do |
10097
|
|
|
{ |
10098
|
|
|
$digit = $this->base64Decode($encoded[$i]); |
10099
|
|
|
$vlq |= ($digit & $this->mask) << ($i * $this->shift); |
10100
|
|
|
$i++; |
10101
|
|
|
} while($digit & $this->continuationBit); |
10102
|
|
|
|
10103
|
|
|
return $this->fromVLQSigned($vlq); |
10104
|
|
|
} |
10105
|
|
|
|
10106
|
|
|
/** |
10107
|
|
|
* Right shift with zero fill. |
10108
|
|
|
* |
10109
|
|
|
* @param integer $a number to shift |
10110
|
|
|
* @param integer $b number of bits to shift |
10111
|
|
|
* @return integer |
10112
|
|
|
*/ |
10113
|
|
|
public function zeroFill($a, $b){ |
10114
|
|
|
return ($a >= 0) ? ($a >> $b) : ($a >> $b) & (PHP_INT_MAX >> ($b - 1)); |
10115
|
|
|
} |
10116
|
|
|
|
10117
|
|
|
/** |
10118
|
|
|
* Encode single 6-bit digit as base64. |
10119
|
|
|
* |
10120
|
|
|
* @param integer $number |
10121
|
|
|
* @return string |
10122
|
|
|
* @throws Exception If the number is invalid |
10123
|
|
|
*/ |
10124
|
|
|
public function base64Encode($number){ |
10125
|
|
|
if($number < 0 || $number > 63){ |
10126
|
|
|
throw new Exception(sprintf('Invalid number "%s" given. Must be between 0 and 63.', $number)); |
10127
|
|
|
} |
10128
|
|
|
return $this->intToCharMap[$number]; |
10129
|
|
|
} |
10130
|
|
|
|
10131
|
|
|
/** |
10132
|
|
|
* Decode single 6-bit digit from base64 |
10133
|
|
|
* |
10134
|
|
|
* @param string $char |
10135
|
|
|
* @return number |
10136
|
|
|
* @throws Exception If the number is invalid |
10137
|
|
|
*/ |
10138
|
|
|
public function base64Decode($char){ |
10139
|
|
|
if(!array_key_exists($char, $this->charToIntMap)){ |
10140
|
|
|
throw new Exception(sprintf('Invalid base 64 digit "%s" given.', $char)); |
10141
|
|
|
} |
10142
|
|
|
return $this->charToIntMap[$char]; |
10143
|
|
|
} |
10144
|
|
|
|
10145
|
|
|
} |
10146
|
|
|
|
10147
|
|
|
|
10148
|
|
|
/** |
10149
|
|
|
* Source map generator |
10150
|
|
|
* |
10151
|
|
|
* @package Less |
10152
|
|
|
* @subpackage Output |
10153
|
|
|
*/ |
10154
|
|
|
class Less_SourceMap_Generator extends Less_Configurable { |
10155
|
|
|
|
10156
|
|
|
/** |
10157
|
|
|
* What version of source map does the generator generate? |
10158
|
|
|
*/ |
10159
|
|
|
const VERSION = 3; |
10160
|
|
|
|
10161
|
|
|
/** |
10162
|
|
|
* Array of default options |
10163
|
|
|
* |
10164
|
|
|
* @var array |
10165
|
|
|
*/ |
10166
|
|
|
protected $defaultOptions = array( |
10167
|
|
|
// an optional source root, useful for relocating source files |
10168
|
|
|
// on a server or removing repeated values in the 'sources' entry. |
10169
|
|
|
// This value is prepended to the individual entries in the 'source' field. |
10170
|
|
|
'sourceRoot' => '', |
10171
|
|
|
|
10172
|
|
|
// an optional name of the generated code that this source map is associated with. |
10173
|
|
|
'sourceMapFilename' => null, |
10174
|
|
|
|
10175
|
|
|
// url of the map |
10176
|
|
|
'sourceMapURL' => null, |
10177
|
|
|
|
10178
|
|
|
// absolute path to a file to write the map to |
10179
|
|
|
'sourceMapWriteTo' => null, |
10180
|
|
|
|
10181
|
|
|
// output source contents? |
10182
|
|
|
'outputSourceFiles' => false, |
10183
|
|
|
|
10184
|
|
|
// base path for filename normalization |
10185
|
|
|
'sourceMapRootpath' => '', |
10186
|
|
|
|
10187
|
|
|
// base path for filename normalization |
10188
|
|
|
'sourceMapBasepath' => '' |
10189
|
|
|
); |
10190
|
|
|
|
10191
|
|
|
/** |
10192
|
|
|
* The base64 VLQ encoder |
10193
|
|
|
* |
10194
|
|
|
* @var Less_SourceMap_Base64VLQ |
10195
|
|
|
*/ |
10196
|
|
|
protected $encoder; |
10197
|
|
|
|
10198
|
|
|
/** |
10199
|
|
|
* Array of mappings |
10200
|
|
|
* |
10201
|
|
|
* @var array |
10202
|
|
|
*/ |
10203
|
|
|
protected $mappings = array(); |
10204
|
|
|
|
10205
|
|
|
/** |
10206
|
|
|
* The root node |
10207
|
|
|
* |
10208
|
|
|
* @var Less_Tree_Ruleset |
10209
|
|
|
*/ |
10210
|
|
|
protected $root; |
10211
|
|
|
|
10212
|
|
|
/** |
10213
|
|
|
* Array of contents map |
10214
|
|
|
* |
10215
|
|
|
* @var array |
10216
|
|
|
*/ |
10217
|
|
|
protected $contentsMap = array(); |
10218
|
|
|
|
10219
|
|
|
/** |
10220
|
|
|
* File to content map |
10221
|
|
|
* |
10222
|
|
|
* @var array |
10223
|
|
|
*/ |
10224
|
|
|
protected $sources = array(); |
10225
|
|
|
protected $source_keys = array(); |
10226
|
|
|
|
10227
|
|
|
/** |
10228
|
|
|
* Constructor |
10229
|
|
|
* |
10230
|
|
|
* @param Less_Tree_Ruleset $root The root node |
10231
|
|
|
* @param array $options Array of options |
10232
|
|
|
*/ |
10233
|
|
|
public function __construct(Less_Tree_Ruleset $root, $contentsMap, $options = array()){ |
10234
|
|
|
$this->root = $root; |
10235
|
|
|
$this->contentsMap = $contentsMap; |
10236
|
|
|
$this->encoder = new Less_SourceMap_Base64VLQ(); |
10237
|
|
|
|
10238
|
|
|
$this->SetOptions($options); |
10239
|
|
|
|
10240
|
|
|
$this->options['sourceMapRootpath'] = $this->fixWindowsPath($this->options['sourceMapRootpath'], true); |
10241
|
|
|
$this->options['sourceMapBasepath'] = $this->fixWindowsPath($this->options['sourceMapBasepath'], true); |
10242
|
|
|
} |
10243
|
|
|
|
10244
|
|
|
/** |
10245
|
|
|
* Generates the CSS |
10246
|
|
|
* |
10247
|
|
|
* @return string |
10248
|
|
|
*/ |
10249
|
|
|
public function generateCSS(){ |
10250
|
|
|
$output = new Less_Output_Mapped($this->contentsMap, $this); |
10251
|
|
|
|
10252
|
|
|
// catch the output |
10253
|
|
|
$this->root->genCSS($output); |
10254
|
|
|
|
10255
|
|
|
|
10256
|
|
|
$sourceMapUrl = $this->getOption('sourceMapURL'); |
10257
|
|
|
$sourceMapFilename = $this->getOption('sourceMapFilename'); |
10258
|
|
|
$sourceMapContent = $this->generateJson(); |
10259
|
|
|
$sourceMapWriteTo = $this->getOption('sourceMapWriteTo'); |
10260
|
|
|
|
10261
|
|
|
if( !$sourceMapUrl && $sourceMapFilename ){ |
10262
|
|
|
$sourceMapUrl = $this->normalizeFilename($sourceMapFilename); |
10263
|
|
|
} |
10264
|
|
|
|
10265
|
|
|
// write map to a file |
10266
|
|
|
if( $sourceMapWriteTo ){ |
10267
|
|
|
$this->saveMap($sourceMapWriteTo, $sourceMapContent); |
10268
|
|
|
} |
10269
|
|
|
|
10270
|
|
|
// inline the map |
10271
|
|
|
if( !$sourceMapUrl ){ |
10272
|
|
|
$sourceMapUrl = sprintf('data:application/json,%s', Less_Functions::encodeURIComponent($sourceMapContent)); |
10273
|
|
|
} |
10274
|
|
|
|
10275
|
|
|
if( $sourceMapUrl ){ |
10276
|
|
|
$output->add( sprintf('/*# sourceMappingURL=%s */', $sourceMapUrl) ); |
10277
|
|
|
} |
10278
|
|
|
|
10279
|
|
|
return $output->toString(); |
10280
|
|
|
} |
10281
|
|
|
|
10282
|
|
|
/** |
10283
|
|
|
* Saves the source map to a file |
10284
|
|
|
* |
10285
|
|
|
* @param string $file The absolute path to a file |
10286
|
|
|
* @param string $content The content to write |
10287
|
|
|
* @throws Exception If the file could not be saved |
10288
|
|
|
*/ |
10289
|
|
|
protected function saveMap($file, $content){ |
10290
|
|
|
$dir = dirname($file); |
10291
|
|
|
// directory does not exist |
10292
|
|
|
if( !is_dir($dir) ){ |
10293
|
|
|
// FIXME: create the dir automatically? |
10294
|
|
|
throw new Exception(sprintf('The directory "%s" does not exist. Cannot save the source map.', $dir)); |
10295
|
|
|
} |
10296
|
|
|
// FIXME: proper saving, with dir write check! |
10297
|
|
|
if(file_put_contents($file, $content) === false){ |
10298
|
|
|
throw new Exception(sprintf('Cannot save the source map to "%s"', $file)); |
10299
|
|
|
} |
10300
|
|
|
return true; |
10301
|
|
|
} |
10302
|
|
|
|
10303
|
|
|
/** |
10304
|
|
|
* Normalizes the filename |
10305
|
|
|
* |
10306
|
|
|
* @param string $filename |
10307
|
|
|
* @return string |
10308
|
|
|
*/ |
10309
|
|
|
protected function normalizeFilename($filename){ |
10310
|
|
|
|
10311
|
|
|
$filename = $this->fixWindowsPath($filename); |
10312
|
|
|
|
10313
|
|
|
$rootpath = $this->getOption('sourceMapRootpath'); |
10314
|
|
|
$basePath = $this->getOption('sourceMapBasepath'); |
10315
|
|
|
|
10316
|
|
|
// "Trim" the 'sourceMapBasepath' from the output filename. |
10317
|
|
|
if (strpos($filename, $basePath) === 0) { |
10318
|
|
|
$filename = substr($filename, strlen($basePath)); |
10319
|
|
|
} |
10320
|
|
|
|
10321
|
|
|
// Remove extra leading path separators. |
10322
|
|
|
if(strpos($filename, '\\') === 0 || strpos($filename, '/') === 0){ |
10323
|
|
|
$filename = substr($filename, 1); |
10324
|
|
|
} |
10325
|
|
|
|
10326
|
|
|
return $rootpath . $filename; |
10327
|
|
|
} |
10328
|
|
|
|
10329
|
|
|
/** |
10330
|
|
|
* Adds a mapping |
10331
|
|
|
* |
10332
|
|
|
* @param integer $generatedLine The line number in generated file |
10333
|
|
|
* @param integer $generatedColumn The column number in generated file |
10334
|
|
|
* @param integer $originalLine The line number in original file |
10335
|
|
|
* @param integer $originalColumn The column number in original file |
10336
|
|
|
* @param string $sourceFile The original source file |
10337
|
|
|
*/ |
10338
|
|
|
public function addMapping($generatedLine, $generatedColumn, $originalLine, $originalColumn, $fileInfo ){ |
10339
|
|
|
|
10340
|
|
|
$this->mappings[] = array( |
10341
|
|
|
'generated_line' => $generatedLine, |
10342
|
|
|
'generated_column' => $generatedColumn, |
10343
|
|
|
'original_line' => $originalLine, |
10344
|
|
|
'original_column' => $originalColumn, |
10345
|
|
|
'source_file' => $fileInfo['currentUri'] |
10346
|
|
|
); |
10347
|
|
|
|
10348
|
|
|
$this->sources[$fileInfo['currentUri']] = $fileInfo['filename']; |
10349
|
|
|
} |
10350
|
|
|
|
10351
|
|
|
|
10352
|
|
|
/** |
10353
|
|
|
* Generates the JSON source map |
10354
|
|
|
* |
10355
|
|
|
* @return string |
10356
|
|
|
* @see https://docs.google.com/document/d/1U1RGAehQwRypUTovF1KRlpiOFze0b-_2gc6fAH0KY0k/edit# |
10357
|
|
|
*/ |
10358
|
|
|
protected function generateJson(){ |
10359
|
|
|
|
10360
|
|
|
$sourceMap = array(); |
10361
|
|
|
$mappings = $this->generateMappings(); |
10362
|
|
|
|
10363
|
|
|
// File version (always the first entry in the object) and must be a positive integer. |
10364
|
|
|
$sourceMap['version'] = self::VERSION; |
10365
|
|
|
|
10366
|
|
|
|
10367
|
|
|
// An optional name of the generated code that this source map is associated with. |
10368
|
|
|
$file = $this->getOption('sourceMapFilename'); |
10369
|
|
|
if( $file ){ |
10370
|
|
|
$sourceMap['file'] = $file; |
10371
|
|
|
} |
10372
|
|
|
|
10373
|
|
|
|
10374
|
|
|
// An optional source root, useful for relocating source files on a server or removing repeated values in the 'sources' entry. This value is prepended to the individual entries in the 'source' field. |
10375
|
|
|
$root = $this->getOption('sourceRoot'); |
10376
|
|
|
if( $root ){ |
10377
|
|
|
$sourceMap['sourceRoot'] = $root; |
10378
|
|
|
} |
10379
|
|
|
|
10380
|
|
|
|
10381
|
|
|
// A list of original sources used by the 'mappings' entry. |
10382
|
|
|
$sourceMap['sources'] = array(); |
10383
|
|
|
foreach($this->sources as $source_uri => $source_filename){ |
10384
|
|
|
$sourceMap['sources'][] = $this->normalizeFilename($source_filename); |
10385
|
|
|
} |
10386
|
|
|
|
10387
|
|
|
|
10388
|
|
|
// A list of symbol names used by the 'mappings' entry. |
10389
|
|
|
$sourceMap['names'] = array(); |
10390
|
|
|
|
10391
|
|
|
// A string with the encoded mapping data. |
10392
|
|
|
$sourceMap['mappings'] = $mappings; |
10393
|
|
|
|
10394
|
|
|
if( $this->getOption('outputSourceFiles') ){ |
10395
|
|
|
// An optional list of source content, useful when the 'source' can't be hosted. |
10396
|
|
|
// The contents are listed in the same order as the sources above. |
10397
|
|
|
// 'null' may be used if some original sources should be retrieved by name. |
10398
|
|
|
$sourceMap['sourcesContent'] = $this->getSourcesContent(); |
10399
|
|
|
} |
10400
|
|
|
|
10401
|
|
|
// less.js compat fixes |
10402
|
|
|
if( count($sourceMap['sources']) && empty($sourceMap['sourceRoot']) ){ |
10403
|
|
|
unset($sourceMap['sourceRoot']); |
10404
|
|
|
} |
10405
|
|
|
|
10406
|
|
|
return json_encode($sourceMap); |
10407
|
|
|
} |
10408
|
|
|
|
10409
|
|
|
/** |
10410
|
|
|
* Returns the sources contents |
10411
|
|
|
* |
10412
|
|
|
* @return array|null |
10413
|
|
|
*/ |
10414
|
|
|
protected function getSourcesContent(){ |
10415
|
|
|
if(empty($this->sources)){ |
10416
|
|
|
return; |
10417
|
|
|
} |
10418
|
|
|
$content = array(); |
10419
|
|
|
foreach($this->sources as $sourceFile){ |
10420
|
|
|
$content[] = file_get_contents($sourceFile); |
10421
|
|
|
} |
10422
|
|
|
return $content; |
10423
|
|
|
} |
10424
|
|
|
|
10425
|
|
|
/** |
10426
|
|
|
* Generates the mappings string |
10427
|
|
|
* |
10428
|
|
|
* @return string |
10429
|
|
|
*/ |
10430
|
|
|
public function generateMappings(){ |
10431
|
|
|
|
10432
|
|
|
if( !count($this->mappings) ){ |
10433
|
|
|
return ''; |
10434
|
|
|
} |
10435
|
|
|
|
10436
|
|
|
$this->source_keys = array_flip(array_keys($this->sources)); |
10437
|
|
|
|
10438
|
|
|
|
10439
|
|
|
// group mappings by generated line number. |
10440
|
|
|
$groupedMap = $groupedMapEncoded = array(); |
10441
|
|
|
foreach($this->mappings as $m){ |
10442
|
|
|
$groupedMap[$m['generated_line']][] = $m; |
10443
|
|
|
} |
10444
|
|
|
ksort($groupedMap); |
10445
|
|
|
|
10446
|
|
|
$lastGeneratedLine = $lastOriginalIndex = $lastOriginalLine = $lastOriginalColumn = 0; |
10447
|
|
|
|
10448
|
|
|
foreach($groupedMap as $lineNumber => $line_map){ |
10449
|
|
|
while(++$lastGeneratedLine < $lineNumber){ |
10450
|
|
|
$groupedMapEncoded[] = ';'; |
10451
|
|
|
} |
10452
|
|
|
|
10453
|
|
|
$lineMapEncoded = array(); |
10454
|
|
|
$lastGeneratedColumn = 0; |
10455
|
|
|
|
10456
|
|
|
foreach($line_map as $m){ |
10457
|
|
|
$mapEncoded = $this->encoder->encode($m['generated_column'] - $lastGeneratedColumn); |
10458
|
|
|
$lastGeneratedColumn = $m['generated_column']; |
10459
|
|
|
|
10460
|
|
|
// find the index |
10461
|
|
|
if( $m['source_file'] ){ |
10462
|
|
|
$index = $this->findFileIndex($m['source_file']); |
10463
|
|
|
if( $index !== false ){ |
10464
|
|
|
$mapEncoded .= $this->encoder->encode($index - $lastOriginalIndex); |
10465
|
|
|
$lastOriginalIndex = $index; |
10466
|
|
|
|
10467
|
|
|
// lines are stored 0-based in SourceMap spec version 3 |
10468
|
|
|
$mapEncoded .= $this->encoder->encode($m['original_line'] - 1 - $lastOriginalLine); |
10469
|
|
|
$lastOriginalLine = $m['original_line'] - 1; |
10470
|
|
|
|
10471
|
|
|
$mapEncoded .= $this->encoder->encode($m['original_column'] - $lastOriginalColumn); |
10472
|
|
|
$lastOriginalColumn = $m['original_column']; |
10473
|
|
|
} |
10474
|
|
|
} |
10475
|
|
|
|
10476
|
|
|
$lineMapEncoded[] = $mapEncoded; |
10477
|
|
|
} |
10478
|
|
|
|
10479
|
|
|
$groupedMapEncoded[] = implode(',', $lineMapEncoded) . ';'; |
10480
|
|
|
} |
10481
|
|
|
|
10482
|
|
|
return rtrim(implode($groupedMapEncoded), ';'); |
10483
|
|
|
} |
10484
|
|
|
|
10485
|
|
|
/** |
10486
|
|
|
* Finds the index for the filename |
10487
|
|
|
* |
10488
|
|
|
* @param string $filename |
10489
|
|
|
* @return integer|false |
10490
|
|
|
*/ |
10491
|
|
|
protected function findFileIndex($filename){ |
10492
|
|
|
return $this->source_keys[$filename]; |
10493
|
|
|
} |
10494
|
|
|
|
10495
|
|
|
/** |
10496
|
|
|
* fix windows paths |
10497
|
|
|
* @param string $path |
10498
|
|
|
* @return string |
10499
|
|
|
*/ |
10500
|
|
|
public function fixWindowsPath($path, $addEndSlash = false){ |
10501
|
|
|
$slash = ($addEndSlash) ? '/' : ''; |
10502
|
|
|
if( !empty($path) ){ |
10503
|
|
|
$path = str_replace('\\', '/', $path); |
10504
|
|
|
$path = rtrim($path,'/') . $slash; |
10505
|
|
|
} |
10506
|
|
|
|
10507
|
|
|
return $path; |
10508
|
|
|
} |
10509
|
|
|
|
10510
|
|
|
} |
If you suppress an error, we recommend checking for the error condition explicitly: