includes/js-compressor.php
changeset 1 fe660c52c48f
child 16 64e0d3d4cf14
equal deleted inserted replaced
0:902822492a68 1:fe660c52c48f
       
     1 <?php
       
     2 
       
     3 /*
       
     4  * Enano - an open-source CMS capable of wiki functions, Drupal-like sidebar blocks, and everything in between
       
     5  * Version 1.0 (Banshee)
       
     6  * Copyright (C) 2006-2007 Dan Fuhry
       
     7  * Javascript compression library - used to compact the client-side Javascript code (all 72KB of it!) to save some bandwidth
       
     8  *
       
     9  * This program is Free Software; you can redistribute and/or modify it under the terms of the GNU General Public License
       
    10  * as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version.
       
    11  *
       
    12  * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
       
    13  * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for details.
       
    14  *
       
    15  * This class was written by Andrea Giammarchi and was downloaded from PHPClasses.org. The information page stated that
       
    16  * this class is licensed under the GNU General Public License, the terms of which can be found by reading the file
       
    17  * "GPL" included with the Enano package.
       
    18  */
       
    19 
       
    20 /**
       
    21  * JavaScriptCompressor class,
       
    22  *	removes comments or pack JavaScript source[s] code.
       
    23  * ______________________________________________________________
       
    24  * JavaScriptCompressor (just 2 public methods)
       
    25  *    |
       
    26  *    |________ getClean(jsSource:mixed):string
       
    27  *    |         	returns one or more JavaScript code without comments,
       
    28  *    |         	by default removes some spaces too
       
    29  *    |
       
    30  *    |________ getPacked(jsSource:mixed):string
       
    31  *              	returns one or more JavaScript code packed,
       
    32  *	        	using getClean and obfuscating output
       
    33  * --------------------------------------------------------------
       
    34  * Note about $jsSource input varible:
       
    35  * 	this var should be a string (i.e. $jsSource = file_get_contents("myFile.js");)
       
    36  *      should be an array of strings (i.e. array(file_get_contents("1.js"), file_get_contents("2.js"), ... ))
       
    37  *      should be an array with 1 or 2 keys:
       
    38  *      	(i.e. array('code'=>file_get_contents("mySource.js")))
       
    39  *              (i.e. array('code'=>file_get_contents("mySource.js"), 'name'=>'mySource'))
       
    40  *      ... and should be an array of arrays created with theese rules
       
    41  *      array(
       
    42  *		file_get_contents("secret.js"),
       
    43  *              array('code'=>$anotherJS),
       
    44  *              array('code'=>$myJSapplication, 'name'=>'JSApplication V 1.0')
       
    45  *      )
       
    46  *
       
    47  *      The name used on dedicated key, will be write on parsed source header
       
    48  * --------------------------------------------------------------
       
    49  * Note about returned strings:
       
    50  * 	Your browser should wrap very long strings, then don't use
       
    51  *      cut and paste from your browser, save output into your database or directly
       
    52  *      in a file or print them only inside <script> and </script> tags
       
    53  * --------------------------------------------------------------
       
    54  * Note about parser performance:
       
    55  * 	With pure PHP embed code this class should be slow and not really safe
       
    56  *      for your server performance then don't parse JavaScript runtime for each
       
    57  *      file you need and create some "parsed" caching system
       
    58  *      (at least while i've not created a compiled version of theese class functions).
       
    59  *      Here there's a caching system example: http://www.phpclasses.org/browse/package/3158.html
       
    60  * --------------------------------------------------------------
       
    61  * Note about JavaScript packed compatibility:
       
    62  * 	To be sure about compatibility include before every script JSL Library:
       
    63  *      http://www.devpro.it/JSL/
       
    64  * JSL library add some features for old or buggy browsers, one of
       
    65  * those functions is String.replace with function as second argument,
       
    66  * used by JavaScript generated packed code to rebuild original code.
       
    67  *
       
    68  * Remember that KDE 3.5, Safari and IE5 will not work correctly with packed version
       
    69  * if you'll not include JSL.
       
    70  * --------------------------------------------------------------
       
    71  * @Compatibility	>= PHP 4
       
    72  * @Author		Andrea Giammarchi
       
    73  * @see       http://www.devpro.it/
       
    74  * @since	    2006/05/31
       
    75  * @since		  2006/08/01 [requires SourceMap.class.php to parse source faster and better (dojo.js.uncompressed.js file (211Kb) successfull cleaned or packed)]
       
    76  * @version		0.8
       
    77  * Dependencies:
       
    78  *      Server: BaseConvert.class.php
       
    79  *			Server: SourceMap.class.php
       
    80  *			Client: JSL.js (http://www.devpro.it/JSL/)
       
    81  * Convertion is supported by every browser with JSL Library (FF 1+ Opera 8+ and IE5.5+ are supported without JSL too)
       
    82  * @copyright Dean Edwards for his originally idea [dean.edwards.name] and his JavaScript packer
       
    83  */
       
    84 class JavaScriptCompressor {
       
    85 
       
    86 	/**
       
    87 	 * public variables
       
    88          * 	stats:string		after every compression has some informations
       
    89          *      version:string		version of this class
       
    90 	 */
       
    91 	var	$stats = '',
       
    92 		$version = '0.8';
       
    93 
       
    94 	/** 'private' variables, any comment sorry */
       
    95 	var	$__startTime = 0,
       
    96 		$__sourceLength = 0,
       
    97 		$__sourceNewLength = 0,
       
    98 		$__totalSources = 0,
       
    99 		$__sources = array(),
       
   100 		$__delimeter = array(),
       
   101 		$__cleanFinder = array("/(\n|\r)+/", "/( |\t)+/", "/(\n )|( \n)|( \n )/", "/[[:space:]]+(\)|})/", "/(\(|{)[[:space:]]+/", "/[[:space:]]*(;|,|:|<|>|\&|\||\=|\?|\+|\-|\%)[[:space:]]*/", "/\)[[:space:]]+{/", "/}[[:space:]]+\(/"),
       
   102 		$__cleanReplacer = array("\n", " ", "\n", "\\1", "\\1", "\\1", "){", "}("),
       
   103 		$__BC = null,
       
   104 		$__SourceMap = null;
       
   105 
       
   106 	/**
       
   107 	 * public constructor
       
   108          * 	creates a new BaseConvert class variable (base 36)
       
   109 	 */
       
   110 	function JavaScriptCompressor() {
       
   111 		$this->__SourceMap = new SourceMap();
       
   112 		$this->__BC = new BaseConvert('0123456789abcdefghijklmnopqrstuvwxyz');
       
   113 		$this->__delimeter = array(
       
   114 			array('name'=>'doublequote', 'start'=>'"', 'end'=>'"', 'noslash'=>true),
       
   115 			array('name'=>'singlequote', 'start'=>"'", 'end'=>"'", 'noslash'=>true),
       
   116 			array('name'=>'singlelinecomment', 'start'=>'//', 'end'=>array("\n", "\r")),
       
   117 			array('name'=>'multilinecomment', 'start'=>'/*', 'end'=>'*/'),
       
   118 			array('name'=>'regexp', 'start'=>'/', 'end'=>'/', 'match'=>"/^\/[^\n\r]+\/$/", 'noslash'=>true)
       
   119 		);
       
   120 	}
       
   121 
       
   122 	/**
       
   123 	 * public method
       
   124          * 	getClean(mixed [, bool]):string
       
   125          *      compress JavaScript removing comments and somespaces (on by default)
       
   126          * @param	mixed		view example and notes on class comments
       
   127 	 */
       
   128 	function getClean($jsSource) {
       
   129 		return $this->__commonInitMethods($jsSource, false);
       
   130 	}
       
   131 	
       
   132 	/**
       
   133 	 * public method
       
   134          * 	getPacked(mixed):string
       
   135          *      compress JavaScript replaceing words and removing comments and some spaces
       
   136          * @param	mixed		view example and notes on class comments
       
   137 	 */
       
   138 	function getPacked($jsSource) {
       
   139 		return $this->__commonInitMethods($jsSource, true);
       
   140 	}
       
   141 	
       
   142 	/** 'private' methods, any comment sorry */
       
   143 	function __addCleanCode($str) {
       
   144 		return preg_replace($this->__cleanFinder, $this->__cleanReplacer, trim($str));
       
   145 	}
       
   146 	function __addClean(&$arr, &$str, &$start, &$end, $clean) {
       
   147 		if($clean)
       
   148 			array_push($arr, $this->__addCleanCode(substr($str, $start, $end - $start)));
       
   149 		else
       
   150 			array_push($arr, substr($str, $start, $end - $start));
       
   151 	}
       
   152 	function __clean(&$str) {
       
   153 		$len = strlen($str);
       
   154 		$type = '';
       
   155 		$clean = array();
       
   156 		$map = $this->__SourceMap->getMap($str, $this->__delimeter);
       
   157 		for($a = 0, $b = 0, $c = count($map); $a < $c; $a++) {
       
   158 			$type = &$map[$a]['name'];
       
   159 			switch($type) {
       
   160 				case 'code':
       
   161 				case 'regexp':
       
   162 				case 'doublequote':
       
   163 				case 'singlequote':
       
   164 					$this->__addClean($clean, $str, $map[$a]['start'], $map[$a]['end'], ($type === 'code'));
       
   165 					if($type !== 'regexp')
       
   166 						array_push($clean, "\n");	
       
   167 					break;
       
   168 			}
       
   169  		}
       
   170 		return preg_replace("/(\n)+/", "\n", trim(implode('', $clean)));
       
   171 	}
       
   172 	function __commonInitMethods(&$jsSource, $packed) { 
       
   173 		$header = '';
       
   174 		$this->__startTime = $this->__getTime();
       
   175 		$this->__sourceLength = 0;
       
   176 		$this->__sourceManager($jsSource);
       
   177 		for($a = 0, $b = $this->__totalSources; $a < $b; $a++)
       
   178 			$this->__sources[$a]['code'] = $this->__clean($this->__sources[$a]['code']);
       
   179 		$header = $this->__getHeader();
       
   180 		for($a = 0, $b = $this->__totalSources; $a < $b; $a++)
       
   181 			$this->__sources[$a] = &$this->__sources[$a]['code'];
       
   182 		$this->__sources = implode(';', $this->__sources);
       
   183 		if($packed)
       
   184 			$this->__sources = $this->__pack($this->__sources);
       
   185 		$this->__sourceNewLength = strlen($this->__sources);
       
   186 		$this->__setStats();
       
   187 		return $header.$this->__sources;
       
   188 	}
       
   189 	function __getHeader() {
       
   190 		return implode('', array(
       
   191 			'/* ',$this->__getScriptNames(),'JavaScriptCompressor ',$this->version,' [www.devpro.it], ',
       
   192 			'thanks to Dean Edwards for idea [dean.edwards.name]',
       
   193 			" */\r\n"		
       
   194 		));
       
   195 	}
       
   196 	function __getScriptNames() {
       
   197 		$a = 0;
       
   198 		$result = array();
       
   199 		for($b = $this->__totalSources; $a < $b; $a++) {
       
   200 			if($this->__sources[$a]['name'] !== '')
       
   201 				array_push($result, $this->__sources[$a]['name']);
       
   202 		}
       
   203 		$a = count($result);
       
   204 		if($a-- > 0)
       
   205 			$result[$a] .= ' with ';
       
   206 		return $a < 0 ? '' : implode(', ', $result);
       
   207 	}
       
   208 	function __getSize($size, $dec = 2) {
       
   209 		$toEval = '';
       
   210 		$type = array('bytes', 'Kb', 'Mb', 'Gb');
       
   211 		$nsize = $size;
       
   212 		$times = 0;
       
   213 		while($nsize > 1024) {
       
   214 			$nsize = $nsize / 1024;
       
   215 			$toEval .= '/1024';
       
   216 			$times++;
       
   217 		}
       
   218 		if($times === 0)
       
   219 			$fSize = $size.' '.$type[$times];
       
   220 		else {
       
   221 			eval('$size=($size'.$toEval.');');
       
   222 			$fSize =  number_format($size, $dec, '.', '').' '.$type[$times];
       
   223 		}
       
   224 		return $fSize;
       
   225 	}
       
   226 	function __getTime($startTime = null) {
       
   227 		list($usec, $sec) = explode(' ', microtime());
       
   228 		$newtime = (float)$usec + (float)$sec;
       
   229 		if($startTime !== null)
       
   230 			$newtime = number_format(($newtime - $startTime), 3);
       
   231 		return $newtime;
       
   232 	}
       
   233 	function __pack(&$str) {
       
   234 		$container = array();
       
   235 		$str = preg_replace("/(\w+)/e", '$this->__BC->toBase($this->__wordsParser("\\1",$container));', $this->__clean($str));
       
   236 		$str = str_replace("\n", '\n', addslashes($str));
       
   237 		return 'eval(function(A,G){return A.replace(/(\\w+)/g,function(a,b){return G[parseInt(b,36)]})}("'.$str.'","'.implode(',', $container).'".split(",")));';
       
   238 	}
       
   239 	function __setStats() {
       
   240 		$this->stats = implode(' ', array(
       
   241 			$this->__getSize($this->__sourceLength),
       
   242 			'to',
       
   243 			$this->__getSize($this->__sourceNewLength),
       
   244 			'in',
       
   245 			$this->__getTime($this->__startTime),
       
   246 			'seconds'
       
   247 		));
       
   248 	}
       
   249 	function __sourceManager(&$jsSource) {
       
   250 		$b = count($jsSource);
       
   251 		$this->__sources = array();
       
   252 		if(is_string($jsSource))
       
   253 			$this->__sourcePusher($jsSource, '');
       
   254 		elseif(is_array($jsSource) && $b > 0) {
       
   255 			if(isset($jsSource['code']))
       
   256 				$this->__sourcePusher($jsSource['code'], (isset($jsSource['name']) ? $jsSource['name'] : ''));
       
   257 			else {
       
   258 				for($a = 0; $a < $b; $a++) {
       
   259 					if(is_array($jsSource[$a]) && isset($jsSource[$a]['code'], $jsSource[$a]['name']))
       
   260 						$this->__sourcePusher($jsSource[$a]['code'], trim($jsSource[$a]['name']));
       
   261 					elseif(is_string($jsSource[$a]))
       
   262 						$this->__sourcePusher($jsSource[$a], '');
       
   263 				}
       
   264 			}
       
   265 		}
       
   266 		$this->__totalSources = count($this->__sources);
       
   267 	}
       
   268 	function __sourcePusher(&$code, $name) {
       
   269 		$this->__sourceLength += strlen($code);
       
   270 		array_push($this->__sources, array('code'=>$code, 'name'=>$name));
       
   271 	}
       
   272 	function __wordsParser($str, &$d) {
       
   273 		if(is_null($key = array_shift($key = array_keys($d,$str))))
       
   274 			$key = array_push($d, $str) - 1;
       
   275 		return $key;
       
   276 	}
       
   277 }
       
   278 
       
   279 /**
       
   280  * BaseConvert class,
       
   281  *	converts an unsigned base 10 integer to a different base and vice versa.
       
   282  * ______________________________________________________________
       
   283  * BaseConvert
       
   284  *    |
       
   285  *    |________ constructor(newBase:string)
       
   286  *    |         	uses newBase string var for convertion
       
   287  *    |                 [i.e. "0123456789abcdef" for an hex convertion]
       
   288  *    |
       
   289  *    |________ toBase(unsignedInteger:uint):string
       
   290  *    |         	return base value of input
       
   291  *    |
       
   292  *    |________ fromBase(baseString:string):uint
       
   293  *              	return base 10 integer value of base input
       
   294  * --------------------------------------------------------------
       
   295  * REMEMBER: PHP < 6 doesn't work correctly with integer greater than 2147483647 (2^31 - 1)
       
   296  * --------------------------------------------------------------
       
   297  * @Compatibility	>= PHP 4
       
   298  * @Author		Andrea Giammarchi
       
   299  * @Site		http://www.devpro.it/
       
   300  * @Date		2006/06/05
       
   301  * @Version		1.0
       
   302  */
       
   303 
       
   304 class BaseConvert {
       
   305 	
       
   306 	var	$base, $baseLength;
       
   307 	
       
   308 	function BaseConvert($base) {
       
   309 		$this->base = &$base;
       
   310 		$this->baseLength = strlen($base);
       
   311 	}
       
   312 	
       
   313 	function toBase($num) {
       
   314 		$module = 0; $result = '';
       
   315 		while($num) {
       
   316 			$result = $this->base{($module = $num % $this->baseLength)}.$result;
       
   317 			$num = (int)(($num - $module) / $this->baseLength);
       
   318 		}
       
   319 		return $result !== '' ? $result : $this->base{0};
       
   320 	}
       
   321 	
       
   322 	function fromBase($str) {
       
   323 		$pos = 0; $len = strlen($str) - 1; $result = 0;
       
   324 		while($pos < $len)
       
   325 			$result += pow($this->baseLength, ($len - $pos)) * strpos($this->base, $str{($pos++)});
       
   326 		return $len >= 0 ? $result + strpos($this->base, $str{($pos)}) : null;
       
   327 	}
       
   328 }
       
   329 
       
   330 /**
       
   331 * SourceMap class,
       
   332 *    reads a generic language source code and returns its map.
       
   333 * ______________________________________________________________
       
   334 * The SourceMap goals is to create a map of a generic script/program language.
       
   335 * The getMap method returns an array/list of arrays/dictionary/objects
       
   336 * of source map using delimeters variable to map correctly:
       
   337 *  - multi line comments
       
   338 *  - single line comments
       
   339 *  - double quoted strings
       
   340 *  - single quoted strings
       
   341 *  - pure code
       
   342 *  - everything else (for example regexp [/re/] with javascript), just adding a correct delimeter
       
   343 * --------------------------------------------------------------
       
   344 * What about the delimeter
       
   345 *     It's an array/list of arrays/dictionary/obects with some properties to find what you're looking for.
       
   346 *
       
   347 * parameters are:
       
   348 *  - name, the name of the delimeter (i.e. "doublequote")
       
   349 *  - start, one or mode chars to find as start delimeter (i.e. " for double quoted string)
       
   350 *  - end, one or mode chars to find as end delimeter (i.e. " for double quoted string) [end should be an array/list too]
       
   351 *
       
   352 * optional parameters are:
       
   353 *  - noslash, if true find the end of the delimeter only if last char is not slashed (i.e. "string\"test" find " after test)
       
   354 *  - match, if choosed language has regexp, verify if string from start to end matches used regexp (i.e. /^\/[^\n\r]+\/$/ for JavaScript regexp)
       
   355 *
       
   356 * If end parameter is an array, match and noslash are not supported (i.e. ["\n", "\r"] for end delimeter of a single line comment)
       
   357 * --------------------------------------------------------------
       
   358 * What about SourceMap usage
       
   359 *     It should be a good solution to create sintax highlighter, parser,
       
   360 *     verifier or some other source code parsing procedure
       
   361 * --------------------------------------------------------------
       
   362 * What about SourceMap performance script/languages
       
   363 *     I've created different version of this class to test each script/program language performance too.
       
   364 * Python with or without Psyco is actually the faster parser.
       
   365 * However with this PHP version this class has mapped "dojo.js.uncompressed.js" file (about 211Kb) in less than 0.5 second.
       
   366 * Test has been done with embed class and PHP as module, any accelerator was used for this PHP test.
       
   367 * --------------------------------------------------------------
       
   368 * @Compatibility    >= PHP 4
       
   369 * @Author        Andrea Giammarchi
       
   370 * @Site        http://www.devpro.it/
       
   371 * @Date        2006/08/01
       
   372 * @LastMOd        2006/08/01
       
   373 * @Version        0.1
       
   374 * @Application        Last version of JavaScriptCompressor class use this one to map source code.
       
   375 */
       
   376 class SourceMap {
       
   377     
       
   378     /**
       
   379      * public method
       
   380          *     getMap(&$source:string, &$delimeters:array):array
       
   381      * Maps the source code using $delimeters rules and returns map as an array
       
   382          * NOTE: read comments to know more about map and delimeter
       
   383          *
       
   384          * @param    string        generic source code
       
   385          * @param    array        array with nested array with code rules
       
   386      */
       
   387     function getMap(&$source, &$delimeters) {
       
   388         
       
   389         # "unsigned" integer variables
       
   390         $sourcePosition = 0;
       
   391         $delimetersPosition = 0;
       
   392         $findLength = 0;
       
   393         $len = 0;
       
   394         $tempIndex = 0;
       
   395         $sourceLength = strlen($source);
       
   396         $delimetersLength = count($delimeters);
       
   397         
       
   398         # integer variables
       
   399         $tempPosition = -1;
       
   400         $endPosition = -1;
       
   401         
       
   402         # array variables
       
   403         $map = array();
       
   404         $tempMap = array();
       
   405         $tempDelimeter = array();
       
   406         
       
   407         while($sourcePosition < $sourceLength) {
       
   408             $endPosition = -1;
       
   409             for($delimetersPosition = 0; $delimetersPosition < $delimetersLength; $delimetersPosition++) {
       
   410                 $tempPosition = strpos($source, $delimeters[$delimetersPosition]['start'], $sourcePosition);
       
   411                 if($tempPosition !== false && ($tempPosition < $endPosition || $endPosition === -1)) {
       
   412                     $endPosition = $tempPosition;
       
   413                     $tempIndex = $delimetersPosition;
       
   414                 }
       
   415             }
       
   416             if($endPosition !== -1) {
       
   417                 $sourcePosition = $endPosition;
       
   418                 $tempDelimeter = &$delimeters[$tempIndex];
       
   419                 $findLength = strlen($tempDelimeter['start']);
       
   420                 if(is_array($tempDelimeter['end'])) {
       
   421                     $delimetersPosition = 0;
       
   422                     $endPosition = -1;
       
   423                     for($len = count($tempDelimeter['end']); $delimetersPosition < $len; $delimetersPosition++) {
       
   424                         $tempPosition = strpos($source, $tempDelimeter['end'][$delimetersPosition], $sourcePosition + $findLength);
       
   425                         if($tempPosition !== false && ($tempPosition < $endPosition || $endPosition === -1)) {
       
   426                             $endPosition = $tempPosition;
       
   427                             $tempIndex = $delimetersPosition;
       
   428                         }    
       
   429                     }
       
   430                     if($endPosition !== -1)
       
   431                         $endPosition = $endPosition + strlen($tempDelimeter['end'][$tempIndex]);
       
   432                     else
       
   433                         $endPosition = $sourceLength;
       
   434                     array_push($map, array('name'=>$tempDelimeter['name'], 'start'=>$sourcePosition, 'end'=>$endPosition));
       
   435                     $sourcePosition = $endPosition - 1;
       
   436                 }
       
   437                 elseif(isset($tempDelimeter['match'])) {
       
   438                     $tempPosition = strpos($source, $tempDelimeter['end'], $sourcePosition + $findLength);
       
   439                     $len = strlen($tempDelimeter['end']);
       
   440                     if($tempPosition !== false && preg_match($tempDelimeter['match'], substr($source, $sourcePosition, $tempPosition - $sourcePosition + $len))) {
       
   441                         $endPosition = isset($tempDelimeter['noslash']) ? $this->__endCharNoSlash($source, $sourcePosition, $tempDelimeter['end'], $sourceLength) : $tempPosition + $len;
       
   442                         array_push($map, array('name'=>$tempDelimeter['name'], 'start'=>$sourcePosition, 'end'=>$endPosition));
       
   443                         $sourcePosition = $endPosition - 1;
       
   444                     }
       
   445                 }
       
   446                 else {
       
   447                     if(isset($tempDelimeter['noslash']))
       
   448                         $endPosition = $this->__endCharNoSlash($source, $sourcePosition, $tempDelimeter['end'], $sourceLength);
       
   449                     else {
       
   450                         $tempPosition = strpos($source, $tempDelimeter['end'], $sourcePosition + $findLength);
       
   451                         if($tempPosition !== false)
       
   452                             $endPosition = $tempPosition + strlen($tempDelimeter['end']);
       
   453                         else
       
   454                             $endPosition = $sourceLength;
       
   455                     }
       
   456                     array_push($map, array('name'=>$tempDelimeter['name'], 'start'=>$sourcePosition, 'end'=>$endPosition));
       
   457                     $sourcePosition = $endPosition - 1;
       
   458                 }
       
   459             }
       
   460             else
       
   461                 $sourcePosition = $sourceLength - 1;
       
   462             ++$sourcePosition;
       
   463         }
       
   464         $len = count($map);
       
   465         if($len === 0)
       
   466             array_push($tempMap, array('name'=>'code', 'start'=>0, 'end'=>$sourceLength));
       
   467         else {
       
   468             for($tempIndex = 0; $tempIndex < $len; $tempIndex++) {
       
   469                 if($tempIndex === 0 && $map[$tempIndex]['start'] > 0)
       
   470                     array_push($tempMap, array('name'=>'code', 'start'=>0, 'end'=>$map[$tempIndex]['start']));
       
   471                 elseif($tempIndex > 0 && $map[$tempIndex]['start'] > $map[$tempIndex-1]['end'])
       
   472                     array_push($tempMap, array('name'=>'code', 'start'=>$map[$tempIndex-1]['end'], 'end'=>$map[$tempIndex]['start']));
       
   473                 array_push($tempMap, array('name'=>$map[$tempIndex]['name'], 'start'=>$map[$tempIndex]['start'], 'end'=>$map[$tempIndex]['end']));
       
   474                 if($tempIndex + 1 === $len && $map[$tempIndex]['end'] < $sourceLength)
       
   475                     array_push($tempMap, array('name'=>'code', 'start'=>$map[$tempIndex]['end'], 'end'=>$sourceLength));
       
   476             }
       
   477         }
       
   478         return $tempMap;
       
   479     }
       
   480     
       
   481     function __endCharNoSlash(&$source, $position, &$find, &$len) {
       
   482         $temp = strlen($find);
       
   483         do {
       
   484             $position = strpos($source, $find, $position + 1);
       
   485         }while($position !== false && !$this->__charNoSlash($source, $position));
       
   486         if($position === false) $position = $len - $temp;
       
   487         return $position + $temp;
       
   488     }
       
   489     
       
   490     function __charNoSlash(&$source, &$position) {
       
   491         $next = 1; $len = $position - $next;
       
   492         while($len > 0 && $source{$len} === '\\') $len = $position - (++$next);
       
   493         return (($next - 1) % 2 === 0);
       
   494     }
       
   495 }
       
   496 ?>