[ Index ] |
PHP Cross Reference of Unnamed Project |
[Summary view] [Print] [Text view]
1 <?php 2 3 /** 4 * Source map generator 5 * 6 * @package Less 7 * @subpackage Output 8 */ 9 class Less_SourceMap_Generator extends Less_Configurable { 10 11 /** 12 * What version of source map does the generator generate? 13 */ 14 const VERSION = 3; 15 16 /** 17 * Array of default options 18 * 19 * @var array 20 */ 21 protected $defaultOptions = array( 22 // an optional source root, useful for relocating source files 23 // on a server or removing repeated values in the 'sources' entry. 24 // This value is prepended to the individual entries in the 'source' field. 25 'sourceRoot' => '', 26 27 // an optional name of the generated code that this source map is associated with. 28 'sourceMapFilename' => null, 29 30 // url of the map 31 'sourceMapURL' => null, 32 33 // absolute path to a file to write the map to 34 'sourceMapWriteTo' => null, 35 36 // output source contents? 37 'outputSourceFiles' => false, 38 39 // base path for filename normalization 40 'sourceMapRootpath' => '', 41 42 // base path for filename normalization 43 'sourceMapBasepath' => '' 44 ); 45 46 /** 47 * The base64 VLQ encoder 48 * 49 * @var Less_SourceMap_Base64VLQ 50 */ 51 protected $encoder; 52 53 /** 54 * Array of mappings 55 * 56 * @var array 57 */ 58 protected $mappings = array(); 59 60 /** 61 * The root node 62 * 63 * @var Less_Tree_Ruleset 64 */ 65 protected $root; 66 67 /** 68 * Array of contents map 69 * 70 * @var array 71 */ 72 protected $contentsMap = array(); 73 74 /** 75 * File to content map 76 * 77 * @var array 78 */ 79 protected $sources = array(); 80 protected $source_keys = array(); 81 82 /** 83 * Constructor 84 * 85 * @param Less_Tree_Ruleset $root The root node 86 * @param array $options Array of options 87 */ 88 public function __construct(Less_Tree_Ruleset $root, $contentsMap, $options = array()){ 89 $this->root = $root; 90 $this->contentsMap = $contentsMap; 91 $this->encoder = new Less_SourceMap_Base64VLQ(); 92 93 $this->SetOptions($options); 94 95 $this->options['sourceMapRootpath'] = $this->fixWindowsPath($this->options['sourceMapRootpath'], true); 96 $this->options['sourceMapBasepath'] = $this->fixWindowsPath($this->options['sourceMapBasepath'], true); 97 } 98 99 /** 100 * Generates the CSS 101 * 102 * @return string 103 */ 104 public function generateCSS(){ 105 $output = new Less_Output_Mapped($this->contentsMap, $this); 106 107 // catch the output 108 $this->root->genCSS($output); 109 110 111 $sourceMapUrl = $this->getOption('sourceMapURL'); 112 $sourceMapFilename = $this->getOption('sourceMapFilename'); 113 $sourceMapContent = $this->generateJson(); 114 $sourceMapWriteTo = $this->getOption('sourceMapWriteTo'); 115 116 if( !$sourceMapUrl && $sourceMapFilename ){ 117 $sourceMapUrl = $this->normalizeFilename($sourceMapFilename); 118 } 119 120 // write map to a file 121 if( $sourceMapWriteTo ){ 122 $this->saveMap($sourceMapWriteTo, $sourceMapContent); 123 } 124 125 // inline the map 126 if( !$sourceMapUrl ){ 127 $sourceMapUrl = sprintf('data:application/json,%s', Less_Functions::encodeURIComponent($sourceMapContent)); 128 } 129 130 if( $sourceMapUrl ){ 131 $output->add( sprintf('/*# sourceMappingURL=%s */', $sourceMapUrl) ); 132 } 133 134 return $output->toString(); 135 } 136 137 /** 138 * Saves the source map to a file 139 * 140 * @param string $file The absolute path to a file 141 * @param string $content The content to write 142 * @throws Exception If the file could not be saved 143 */ 144 protected function saveMap($file, $content){ 145 $dir = dirname($file); 146 // directory does not exist 147 if( !is_dir($dir) ){ 148 // FIXME: create the dir automatically? 149 throw new Exception(sprintf('The directory "%s" does not exist. Cannot save the source map.', $dir)); 150 } 151 // FIXME: proper saving, with dir write check! 152 if(file_put_contents($file, $content) === false){ 153 throw new Exception(sprintf('Cannot save the source map to "%s"', $file)); 154 } 155 return true; 156 } 157 158 /** 159 * Normalizes the filename 160 * 161 * @param string $filename 162 * @return string 163 */ 164 protected function normalizeFilename($filename){ 165 166 $filename = $this->fixWindowsPath($filename); 167 168 $rootpath = $this->getOption('sourceMapRootpath'); 169 $basePath = $this->getOption('sourceMapBasepath'); 170 171 // "Trim" the 'sourceMapBasepath' from the output filename. 172 if (strpos($filename, $basePath) === 0) { 173 $filename = substr($filename, strlen($basePath)); 174 } 175 176 // Remove extra leading path separators. 177 if(strpos($filename, '\\') === 0 || strpos($filename, '/') === 0){ 178 $filename = substr($filename, 1); 179 } 180 181 return $rootpath . $filename; 182 } 183 184 /** 185 * Adds a mapping 186 * 187 * @param integer $generatedLine The line number in generated file 188 * @param integer $generatedColumn The column number in generated file 189 * @param integer $originalLine The line number in original file 190 * @param integer $originalColumn The column number in original file 191 * @param string $sourceFile The original source file 192 */ 193 public function addMapping($generatedLine, $generatedColumn, $originalLine, $originalColumn, $fileInfo ){ 194 195 $this->mappings[] = array( 196 'generated_line' => $generatedLine, 197 'generated_column' => $generatedColumn, 198 'original_line' => $originalLine, 199 'original_column' => $originalColumn, 200 'source_file' => $fileInfo['currentUri'] 201 ); 202 203 $this->sources[$fileInfo['currentUri']] = $fileInfo['filename']; 204 } 205 206 207 /** 208 * Generates the JSON source map 209 * 210 * @return string 211 * @see https://docs.google.com/document/d/1U1RGAehQwRypUTovF1KRlpiOFze0b-_2gc6fAH0KY0k/edit# 212 */ 213 protected function generateJson(){ 214 215 $sourceMap = array(); 216 $mappings = $this->generateMappings(); 217 218 // File version (always the first entry in the object) and must be a positive integer. 219 $sourceMap['version'] = self::VERSION; 220 221 222 // An optional name of the generated code that this source map is associated with. 223 $file = $this->getOption('sourceMapFilename'); 224 if( $file ){ 225 $sourceMap['file'] = $file; 226 } 227 228 229 // 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. 230 $root = $this->getOption('sourceRoot'); 231 if( $root ){ 232 $sourceMap['sourceRoot'] = $root; 233 } 234 235 236 // A list of original sources used by the 'mappings' entry. 237 $sourceMap['sources'] = array(); 238 foreach($this->sources as $source_uri => $source_filename){ 239 $sourceMap['sources'][] = $this->normalizeFilename($source_filename); 240 } 241 242 243 // A list of symbol names used by the 'mappings' entry. 244 $sourceMap['names'] = array(); 245 246 // A string with the encoded mapping data. 247 $sourceMap['mappings'] = $mappings; 248 249 if( $this->getOption('outputSourceFiles') ){ 250 // An optional list of source content, useful when the 'source' can't be hosted. 251 // The contents are listed in the same order as the sources above. 252 // 'null' may be used if some original sources should be retrieved by name. 253 $sourceMap['sourcesContent'] = $this->getSourcesContent(); 254 } 255 256 // less.js compat fixes 257 if( count($sourceMap['sources']) && empty($sourceMap['sourceRoot']) ){ 258 unset($sourceMap['sourceRoot']); 259 } 260 261 return json_encode($sourceMap); 262 } 263 264 /** 265 * Returns the sources contents 266 * 267 * @return array|null 268 */ 269 protected function getSourcesContent(){ 270 if(empty($this->sources)){ 271 return; 272 } 273 $content = array(); 274 foreach($this->sources as $sourceFile){ 275 $content[] = file_get_contents($sourceFile); 276 } 277 return $content; 278 } 279 280 /** 281 * Generates the mappings string 282 * 283 * @return string 284 */ 285 public function generateMappings(){ 286 287 if( !count($this->mappings) ){ 288 return ''; 289 } 290 291 $this->source_keys = array_flip(array_keys($this->sources)); 292 293 294 // group mappings by generated line number. 295 $groupedMap = $groupedMapEncoded = array(); 296 foreach($this->mappings as $m){ 297 $groupedMap[$m['generated_line']][] = $m; 298 } 299 ksort($groupedMap); 300 301 $lastGeneratedLine = $lastOriginalIndex = $lastOriginalLine = $lastOriginalColumn = 0; 302 303 foreach($groupedMap as $lineNumber => $line_map){ 304 while(++$lastGeneratedLine < $lineNumber){ 305 $groupedMapEncoded[] = ';'; 306 } 307 308 $lineMapEncoded = array(); 309 $lastGeneratedColumn = 0; 310 311 foreach($line_map as $m){ 312 $mapEncoded = $this->encoder->encode($m['generated_column'] - $lastGeneratedColumn); 313 $lastGeneratedColumn = $m['generated_column']; 314 315 // find the index 316 if( $m['source_file'] ){ 317 $index = $this->findFileIndex($m['source_file']); 318 if( $index !== false ){ 319 $mapEncoded .= $this->encoder->encode($index - $lastOriginalIndex); 320 $lastOriginalIndex = $index; 321 322 // lines are stored 0-based in SourceMap spec version 3 323 $mapEncoded .= $this->encoder->encode($m['original_line'] - 1 - $lastOriginalLine); 324 $lastOriginalLine = $m['original_line'] - 1; 325 326 $mapEncoded .= $this->encoder->encode($m['original_column'] - $lastOriginalColumn); 327 $lastOriginalColumn = $m['original_column']; 328 } 329 } 330 331 $lineMapEncoded[] = $mapEncoded; 332 } 333 334 $groupedMapEncoded[] = implode(',', $lineMapEncoded) . ';'; 335 } 336 337 return rtrim(implode($groupedMapEncoded), ';'); 338 } 339 340 /** 341 * Finds the index for the filename 342 * 343 * @param string $filename 344 * @return integer|false 345 */ 346 protected function findFileIndex($filename){ 347 return $this->source_keys[$filename]; 348 } 349 350 /** 351 * fix windows paths 352 * @param string $path 353 * @return string 354 */ 355 public function fixWindowsPath($path, $addEndSlash = false){ 356 $slash = ($addEndSlash) ? '/' : ''; 357 if( !empty($path) ){ 358 $path = str_replace('\\', '/', $path); 359 $path = rtrim($path,'/') . $slash; 360 } 361 362 return $path; 363 } 364 365 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Thu Aug 11 10:00:09 2016 | Cross-referenced by PHPXref 0.7.1 |