1 | <?php | 1 | <?php |
2 | /* 7 December 2006. version 1.0 | 2 | /* 9 April 2008. version 1.1 |
3 | * | 3 | * |
4 | * This is the php version of the Dean Edwards JavaScript 's Packer, | 4 | * This is the php version of the Dean Edwards JavaScript's Packer, |
5 | * Based on : | 5 | * Based on : |
6 | * | 6 | * |
7 | * ParseMaster, version 1.0.2 (2005-08-19) Copyright 2005, Dean Edwards | 7 | * ParseMaster, version 1.0.2 (2005-08-19) Copyright 2005, Dean Edwards |
8 | * a multi-pattern parser. | 8 | * a multi-pattern parser. |
9 | * KNOWN BUG: erroneous behavior when using escapeChar with a replacement | 9 | * KNOWN BUG: erroneous behavior when using escapeChar with a replacement |
10 | * value that is a function | 10 | * value that is a function |
11 | * | 11 | * |
12 | * packer, version 2.0.2 (2005-08-19) Copyright 2004-2005, Dean Edwards | 12 | * packer, version 2.0.2 (2005-08-19) Copyright 2004-2005, Dean Edwards |
13 | * | 13 | * |
14 | * License: http://creativecommons.org/licenses/LGPL/2.1/ | 14 | * License: http://creativecommons.org/licenses/LGPL/2.1/ |
15 | * | 15 | * |
16 | * Ported to PHP by Nicolas Martin. | 16 | * Ported to PHP by Nicolas Martin. |
17 | * | 17 | * |
18 | * ---------------------------------------------------------------------- | 18 | * ---------------------------------------------------------------------- |
19 | * | 19 | * changelog: |
20 | * examples of usage : | 20 | * 1.1 : correct a bug, '\0' packed then unpacked becomes '\'. |
21 | * $myPacker = new JavaScriptPacker($script, 62, true, false); | 21 | * ---------------------------------------------------------------------- |
22 | * $packed = $myPacker->pack(); | 22 | * |
23 | * | 23 | * examples of usage : |
24 | * or | 24 | * $myPacker = new JavaScriptPacker($script, 62, true, false); |
25 | * | 25 | * $packed = $myPacker->pack(); |
26 | * $myPacker = new JavaScriptPacker($script, 'Normal', true, false); | 26 | * |
27 | * $packed = $myPacker->pack(); | 27 | * or |
28 | * | 28 | * |
29 | * or (default values) | 29 | * $myPacker = new JavaScriptPacker($script, 'Normal', true, false); |
30 | * | 30 | * $packed = $myPacker->pack(); |
31 | * $myPacker = new JavaScriptPacker($script); | 31 | * |
32 | * $packed = $myPacker->pack(); | 32 | * or (default values) |
33 | * | 33 | * |
34 | * | 34 | * $myPacker = new JavaScriptPacker($script); |
35 | * params of the constructor : | 35 | * $packed = $myPacker->pack(); |
36 | * $script: the JavaScript to pack, string. | 36 | * |
37 | * $encoding: level of encoding, int or string : | 37 | * |
38 | * 0,10,62,95 or 'None', 'Numeric', 'Normal', 'High ASCII'. | 38 | * params of the constructor : |
39 | * default: 62. | 39 | * $script: the JavaScript to pack, string. |
40 | * $fastDecode: include the fast decoder in the packed result, boolean. | 40 | * $encoding: level of encoding, int or string : |
41 | * default : true. | 41 | * 0,10,62,95 or 'None', 'Numeric', 'Normal', 'High ASCII'. |
42 | * $specialChars: if you are flagged your private and local variables | 42 | * default: 62. |
43 | * in the script, boolean. | 43 | * $fastDecode: include the fast decoder in the packed result, boolean. |
44 | * default: false. | 44 | * default : true. |
45 | * | 45 | * $specialChars: if you are flagged your private and local variables |
46 | * The pack() method return the compressed JavasScript, as a string. | 46 | * in the script, boolean. |
47 | * | 47 | * default: false. |
48 | * see http://dean.edwards.name/packer/usage/ for more information. | 48 | * |
49 | * | 49 | * The pack() method return the compressed JavasScript, as a string. |
50 | * Notes : | 50 | * |
51 | * # need PHP 5 . Tested with PHP 5.1.2 | 51 | * see http://dean.edwards.name/packer/usage/ for more information. |
52 | * | 52 | * |
53 | * # The packed result may be different than with the Dean Edwards | 53 | * Notes : |
54 | * version, but with the same length. The reason is that the PHP | 54 | * # need PHP 5 . Tested with PHP 5.1.2, 5.1.3, 5.1.4, 5.2.3 |
55 | * function usort to sort array don't necessarily preserve the | 55 | * |
56 | * original order of two equal member. The Javascript sort function | 56 | * # The packed result may be different than with the Dean Edwards |
57 | * in fact preserve this order (but that's not require by the | 57 | * version, but with the same length. The reason is that the PHP |
58 | * ECMAScript standard). So the encoded keywords order can be | 58 | * function usort to sort array don't necessarily preserve the |
59 | * different in the two results. | 59 | * original order of two equal member. The Javascript sort function |
60 | * | 60 | * in fact preserve this order (but that's not require by the |
61 | * # Be careful with the 'High ASCII' Level encoding if you use | 61 | * ECMAScript standard). So the encoded keywords order can be |
62 | * UTF-8 in your files... | 62 | * different in the two results. |
63 | */ | 63 | * |
64 | | 64 | * # Be careful with the 'High ASCII' Level encoding if you use |
65 | | 65 | * UTF-8 in your files... |
66 | class JavaScriptPacker { | 66 | */ |
67 | // constants | 67 | |
68 | const IGNORE = '$1'; | 68 | |
69 | | 69 | class JavaScriptPacker { |
70 | // validate parameters | 70 | // constants |
71 | private $_script = ''; | 71 | const IGNORE = '$1'; |
72 | private $_encoding = 62; | 72 | |
73 | private $_fastDecode = true; | 73 | // validate parameters |
74 | private $_specialChars = false; | 74 | private $_script = ''; |
75 | | 75 | private $_encoding = 62; |
76 | private $LITERAL_ENCODING = array( | 76 | private $_fastDecode = true; |
77 | 'None' => 0, | 77 | private $_specialChars = false; |
78 | 'Numeric' => 10, | 78 | |
79 | 'Normal' => 62, | 79 | private $LITERAL_ENCODING = array( |
80 | 'High ASCII' => 95 | 80 | 'None' => 0, |
81 | ); | 81 | 'Numeric' => 10, |
82 | | 82 | 'Normal' => 62, |
83 | public function __construct($_script, $_encoding = 62, $_fastDecode = true, $_specialChars = false) | 83 | 'High ASCII' => 95 |
84 | { | 84 | ); |
85 | $this->_script = $_script . "\n"; | 85 | |
86 | if (array_key_exists($_encoding, $this->LITERAL_ENCODING)) | 86 | public function __construct($_script, $_encoding = 62, $_fastDecode = true, $_specialChars = false) |
87 | $_encoding = $this->LITERAL_ENCODING[$_encoding]; | 87 | { |
88 | $this->_encoding = min((int)$_encoding, 95); | 88 | $this->_script = $_script . "\n"; |
89 | $this->_fastDecode = $_fastDecode; | 89 | if (array_key_exists($_encoding, $this->LITERAL_ENCODING)) |
90 | $this->_specialChars = $_specialChars; | 90 | $_encoding = $this->LITERAL_ENCODING[$_encoding]; |
91 | } | 91 | $this->_encoding = min((int)$_encoding, 95); |
92 | | 92 | $this->_fastDecode = $_fastDecode; |
93 | public function pack() { | 93 | $this->_specialChars = $_specialChars; |
94 | $this->_addParser('_basicCompression'); | 94 | } |
95 | if ($this->_specialChars) | 95 | |
96 | $this->_addParser('_encodeSpecialChars'); | 96 | public function pack() { |
97 | if ($this->_encoding) | 97 | $this->_addParser('_basicCompression'); |
98 | $this->_addParser('_encodeKeywords'); | 98 | if ($this->_specialChars) |
99 | | 99 | $this->_addParser('_encodeSpecialChars'); |
100 | // go! | 100 | if ($this->_encoding) |
101 | return $this->_pack($this->_script); | 101 | $this->_addParser('_encodeKeywords'); |
102 | } | 102 | |
103 | | 103 | // go! |
104 | // apply all parsing routines | 104 | return $this->_pack($this->_script); |
105 | private function _pack($script) { | 105 | } |
106 | for ($i = 0; isset($this->_parsers[$i]); $i++) { | 106 | |
107 | $script = call_user_func(array(&$this,$this->_parsers[$i]), $script); | 107 | // apply all parsing routines |
108 | } | 108 | private function _pack($script) { |
109 | return $script; | 109 | for ($i = 0; isset($this->_parsers[$i]); $i++) { |
110 | } | 110 | $script = call_user_func(array(&$this,$this->_parsers[$i]), $script); |
111 | | 111 | } |
112 | // keep a list of parsing functions, they'll be executed all at once | 112 | return $script; |
113 | private $_parsers = array(); | 113 | } |
114 | private function _addParser($parser) { | 114 | |
115 | $this->_parsers[] = $parser; | 115 | // keep a list of parsing functions, they'll be executed all at once |
116 | } | 116 | private $_parsers = array(); |
117 | | 117 | private function _addParser($parser) { |
118 | // zero encoding - just removal of white space and comments | 118 | $this->_parsers[] = $parser; |
119 | private function _basicCompression($script) { | 119 | } |
120 | $parser = new ParseMaster(); | 120 | |
121 | // make safe | 121 | // zero encoding - just removal of white space and comments |
122 | $parser->escapeChar = '\\'; | 122 | private function _basicCompression($script) { |
123 | // protect strings | 123 | $parser = new ParseMaster(); |
124 | $parser->add('/\'[^\'\\n\\r]*\'/', self::IGNORE); | 124 | // make safe |
125 | $parser->add('/"[^"\\n\\r]*"/', self::IGNORE); | 125 | $parser->escapeChar = '\\'; |
126 | // remove comments | 126 | // protect strings |
127 | $parser->add('/\\/\\/[^\\n\\r]*[\\n\\r]/', ' '); | 127 | $parser->add('/\'[^\'\\n\\r]*\'/', self::IGNORE); |
128 | $parser->add('/\\/\\*[^*]*\\*+([^\\/][^*]*\\*+)*\\//', ' '); | 128 | $parser->add('/"[^"\\n\\r]*"/', self::IGNORE); |
129 | // protect regular expressions | 129 | // remove comments |
130 | $parser->add('/\\s+(\\/[^\\/\\n\\r\\*][^\\/\\n\\r]*\\/g?i?)/', '$2'); // IGNORE | 130 | $parser->add('/\\/\\/[^\\n\\r]*[\\n\\r]/', ' '); |
131 | $parser->add('/[^\\w\\x24\\/\'"*)\\?:]\\/[^\\/\\n\\r\\*][^\\/\\n\\r]*\\/g?i?/', self::IGNORE); | 131 | $parser->add('/\\/\\*[^*]*\\*+([^\\/][^*]*\\*+)*\\//', ' '); |
132 | // remove: ;;; doSomething(); | 132 | // protect regular expressions |
133 | if ($this->_specialChars) $parser->add('/;;;[^\\n\\r]+[\\n\\r]/'); | 133 | $parser->add('/\\s+(\\/[^\\/\\n\\r\\*][^\\/\\n\\r]*\\/g?i?)/', '$2'); // IGNORE |
134 | // remove redundant semi-colons | 134 | $parser->add('/[^\\w\\x24\\/\'"*)\\?:]\\/[^\\/\\n\\r\\*][^\\/\\n\\r]*\\/g?i?/', self::IGNORE); |
135 | $parser->add('/\\(;;\\)/', self::IGNORE); // protect for (;;) loops | 135 | // remove: ;;; doSomething(); |
136 | $parser->add('/;+\\s*([};])/', '$2'); | 136 | if ($this->_specialChars) $parser->add('/;;;[^\\n\\r]+[\\n\\r]/'); |
137 | // apply the above | 137 | // remove redundant semi-colons |
138 | $script = $parser->exec($script); | 138 | $parser->add('/\\(;;\\)/', self::IGNORE); // protect for (;;) loops |
139 | | 139 | $parser->add('/;+\\s*([};])/', '$2'); |
140 | // remove white-space | 140 | // apply the above |
141 | $parser->add('/(\\b|\\x24)\\s+(\\b|\\x24)/', '$2 $3'); | 141 | $script = $parser->exec($script); |
142 | $parser->add('/([+\\-])\\s+([+\\-])/', '$2 $3'); | 142 | |
143 | $parser->add('/\\s+/', ''); | 143 | // remove white-space |
144 | // done | 144 | $parser->add('/(\\b|\\x24)\\s+(\\b|\\x24)/', '$2 $3'); |
145 | return $parser->exec($script); | 145 | $parser->add('/([+\\-])\\s+([+\\-])/', '$2 $3'); |
146 | } | 146 | $parser->add('/\\s+/', ''); |
147 | | 147 | // done |
148 | private function _encodeSpecialChars($script) { | 148 | return $parser->exec($script); |
149 | $parser = new ParseMaster(); | 149 | } |
150 | // replace: $name -> n, $$name -> na | 150 | |
151 | $parser->add('/((\\x24+)([a-zA-Z$_]+))(\\d*)/', | 151 | private function _encodeSpecialChars($script) { |
152 | array('fn' => '_replace_name') | 152 | $parser = new ParseMaster(); |
153 | ); | 153 | // replace: $name -> n, $$name -> na |
154 | // replace: _name -> _0, double-underscore (__name) is ignored | 154 | $parser->add('/((\\x24+)([a-zA-Z$_]+))(\\d*)/', |
155 | $regexp = '/\\b_[A-Za-z\\d]\\w*/'; | 155 | array('fn' => '_replace_name') |
156 | // build the word list | 156 | ); |
157 | $keywords = $this->_analyze($script, $regexp, '_encodePrivate'); | 157 | // replace: _name -> _0, double-underscore (__name) is ignored |
158 | // quick ref | 158 | $regexp = '/\\b_[A-Za-z\\d]\\w*/'; |
159 | $encoded = $keywords['encoded']; | 159 | // build the word list |
160 | | 160 | $keywords = $this->_analyze($script, $regexp, '_encodePrivate'); |
161 | $parser->add($regexp, | 161 | // quick ref |
162 | array( | 162 | $encoded = $keywords['encoded']; |
163 | 'fn' => '_replace_encoded', | 163 | |
164 | 'data' => $encoded | 164 | $parser->add($regexp, |
165 | ) | 165 | array( |
166 | ); | 166 | 'fn' => '_replace_encoded', |
167 | return $parser->exec($script); | 167 | 'data' => $encoded |
168 | } | 168 | ) |
169 | | 169 | ); |
170 | private function _encodeKeywords($script) { | 170 | return $parser->exec($script); |
171 | // escape high-ascii values already in the script (i.e. in strings) | 171 | } |
172 | if ($this->_encoding > 62) | 172 | |
173 | $script = $this->_escape95($script); | 173 | private function _encodeKeywords($script) { |
174 | // create the parser | 174 | // escape high-ascii values already in the script (i.e. in strings) |
175 | $parser = new ParseMaster(); | 175 | if ($this->_encoding > 62) |
176 | $encode = $this->_getEncoder($this->_encoding); | 176 | $script = $this->_escape95($script); |
177 | // for high-ascii, don't encode single character low-ascii | 177 | // create the parser |
178 | $regexp = ($this->_encoding > 62) ? '/\\w\\w+/' : '/\\w+/'; | 178 | $parser = new ParseMaster(); |
179 | // build the word list | 179 | $encode = $this->_getEncoder($this->_encoding); |
180 | $keywords = $this->_analyze($script, $regexp, $encode); | 180 | // for high-ascii, don't encode single character low-ascii |
181 | $encoded = $keywords['encoded']; | 181 | $regexp = ($this->_encoding > 62) ? '/\\w\\w+/' : '/\\w+/'; |
182 | | 182 | // build the word list |
183 | // encode | 183 | $keywords = $this->_analyze($script, $regexp, $encode); |
184 | $parser->add($regexp, | 184 | $encoded = $keywords['encoded']; |
185 | array( | 185 | |
186 | 'fn' => '_replace_encoded', | 186 | // encode |
187 | 'data' => $encoded | 187 | $parser->add($regexp, |
188 | ) | 188 | array( |
189 | ); | 189 | 'fn' => '_replace_encoded', |
190 | if (empty($script)) return $script; | 190 | 'data' => $encoded |
191 | else { | 191 | ) |
192 | //$res = $parser->exec($script); | 192 | ); |
193 | //$res = $this->_bootStrap($res, $keywords); | 193 | if (empty($script)) return $script; |
194 | //return $res; | 194 | else { |
195 | return $this->_bootStrap($parser->exec($script), $keywords); | 195 | //$res = $parser->exec($script); |
196 | } | 196 | //$res = $this->_bootStrap($res, $keywords); |
197 | } | 197 | //return $res; |
198 | | 198 | return $this->_bootStrap($parser->exec($script), $keywords); |
199 | private function _analyze($script, $regexp, $encode) { | 199 | } |
200 | // analyse | 200 | } |
201 | // retreive all words in the script | 201 | |
202 | $all = array(); | 202 | private function _analyze($script, $regexp, $encode) { |
203 | preg_match_all($regexp, $script, $all); | 203 | // analyse |
204 | $_sorted = array(); // list of words sorted by frequency | 204 | // retreive all words in the script |
205 | $_encoded = array(); // dictionary of word->encoding | 205 | $all = array(); |
206 | $_protected = array(); // instances of "protected" words | 206 | preg_match_all($regexp, $script, $all); |
207 | $all = $all[0]; // simulate the javascript comportement of global match | 207 | $_sorted = array(); // list of words sorted by frequency |
208 | if (!empty($all)) { | 208 | $_encoded = array(); // dictionary of word->encoding |
209 | $unsorted = array(); // same list, not sorted | 209 | $_protected = array(); // instances of "protected" words |
210 | $protected = array(); // "protected" words (dictionary of word->"word") | 210 | $all = $all[0]; // simulate the javascript comportement of global match |
211 | $value = array(); // dictionary of charCode->encoding (eg. 256->ff) | 211 | if (!empty($all)) { |
212 | $this->_count = array(); // word->count | 212 | $unsorted = array(); // same list, not sorted |
213 | $i = count($all); $j = 0; //$word = null; | 213 | $protected = array(); // "protected" words (dictionary of word->"word") |
214 | // count the occurrences - used for sorting later | 214 | $value = array(); // dictionary of charCode->encoding (eg. 256->ff) |
215 | do { | 215 | $this->_count = array(); // word->count |
216 | --$i; | 216 | $i = count($all); $j = 0; //$word = null; |
217 | $word = '$' . $all[$i]; | 217 | // count the occurrences - used for sorting later |
218 | if (!isset($this->_count[$word])) { | 218 | do { |
219 | $this->_count[$word] = 0; | 219 | --$i; |
220 | $unsorted[$j] = $word; | 220 | $word = '$' . $all[$i]; |
221 | // make a dictionary of all of the protected words in this script | 221 | if (!isset($this->_count[$word])) { |
222 | // these are words that might be mistaken for encoding | 222 | $this->_count[$word] = 0; |
223 | //if (is_string($encode) && method_exists($this, $encode)) | 223 | $unsorted[$j] = $word; |
224 | $values[$j] = call_user_func(array(&$this, $encode), $j); | 224 | // make a dictionary of all of the protected words in this script |
225 | $protected['$' . $values[$j]] = $j++; | 225 | // these are words that might be mistaken for encoding |
226 | } | 226 | //if (is_string($encode) && method_exists($this, $encode)) |
227 | // increment the word counter | 227 | $values[$j] = call_user_func(array(&$this, $encode), $j); |
228 | $this->_count[$word]++; | 228 | $protected['$' . $values[$j]] = $j++; |
229 | } while ($i > 0); | 229 | } |
230 | // prepare to sort the word list, first we must protect | 230 | // increment the word counter |
231 | // words that are also used as codes. we assign them a code | 231 | $this->_count[$word]++; |
232 | // equivalent to the word itself. | 232 | } while ($i > 0); |
233 | // e.g. if "do" falls within our encoding range | 233 | // prepare to sort the word list, first we must protect |
234 | // then we store keywords["do"] = "do"; | 234 | // words that are also used as codes. we assign them a code |
235 | // this avoids problems when decoding | 235 | // equivalent to the word itself. |
236 | $i = count($unsorted); | 236 | // e.g. if "do" falls within our encoding range |
237 | do { | 237 | // then we store keywords["do"] = "do"; |
238 | $word = $unsorted[--$i]; | 238 | // this avoids problems when decoding |
239 | if (isset($protected[$word]) /*!= null*/) { | 239 | $i = count($unsorted); |
240 | $_sorted[$protected[$word]] = substr($word, 1); | 240 | do { |
241 | $_protected[$protected[$word]] = true; | 241 | $word = $unsorted[--$i]; |
242 | $this->_count[$word] = 0; | 242 | if (isset($protected[$word]) /*!= null*/) { |
243 | } | 243 | $_sorted[$protected[$word]] = substr($word, 1); |
244 | } while ($i); | 244 | $_protected[$protected[$word]] = true; |
245 | | 245 | $this->_count[$word] = 0; |
246 | // sort the words by frequency | 246 | } |
247 | // Note: the javascript and php version of sort can be different : | 247 | } while ($i); |
248 | // in php manual, usort : | 248 | |
249 | // " If two members compare as equal, | 249 | // sort the words by frequency |
250 | // their order in the sorted array is undefined." | 250 | // Note: the javascript and php version of sort can be different : |
251 | // so the final packed script is different of the Dean's javascript version | 251 | // in php manual, usort : |
252 | // but equivalent. | 252 | // " If two members compare as equal, |
253 | // the ECMAscript standard does not guarantee this behaviour, | 253 | // their order in the sorted array is undefined." |
254 | // and thus not all browsers (e.g. Mozilla versions dating back to at | 254 | // so the final packed script is different of the Dean's javascript version |
255 | // least 2003) respect this. | 255 | // but equivalent. |
256 | usort($unsorted, array(&$this, '_sortWords')); | 256 | // the ECMAscript standard does not guarantee this behaviour, |
257 | $j = 0; | 257 | // and thus not all browsers (e.g. Mozilla versions dating back to at |
258 | // because there are "protected" words in the list | 258 | // least 2003) respect this. |
259 | // we must add the sorted words around them | 259 | usort($unsorted, array(&$this, '_sortWords')); |
260 | do { | 260 | $j = 0; |
261 | if (!isset($_sorted[$i])) | 261 | // because there are "protected" words in the list |
262 | $_sorted[$i] = substr($unsorted[$j++], 1); | 262 | // we must add the sorted words around them |
263 | $_encoded[$_sorted[$i]] = $values[$i]; | 263 | do { |
264 | } while (++$i < count($unsorted)); | 264 | if (!isset($_sorted[$i])) |
265 | } | 265 | $_sorted[$i] = substr($unsorted[$j++], 1); |
266 | return array( | 266 | $_encoded[$_sorted[$i]] = $values[$i]; |
267 | 'sorted' => $_sorted, | 267 | } while (++$i < count($unsorted)); |
268 | 'encoded' => $_encoded, | 268 | } |
269 | 'protected' => $_protected); | 269 | return array( |
270 | } | 270 | 'sorted' => $_sorted, |
271 | | 271 | 'encoded' => $_encoded, |
272 | private $_count = array(); | 272 | 'protected' => $_protected); |
273 | private function _sortWords($match1, $match2) { | 273 | } |
274 | return $this->_count[$match2] - $this->_count[$match1]; | 274 | |
275 | } | 275 | private $_count = array(); |
276 | | 276 | private function _sortWords($match1, $match2) { |
277 | // build the boot function used for loading and decoding | 277 | return $this->_count[$match2] - $this->_count[$match1]; |
278 | private function _bootStrap($packed, $keywords) { | 278 | } |
279 | $ENCODE = $this->_safeRegExp('$encode\\($count\\)'); | 279 | |
280 | | 280 | // build the boot function used for loading and decoding |
281 | // $packed: the packed script | 281 | private function _bootStrap($packed, $keywords) { |
282 | $packed = "'" . $this->_escape($packed) . "'"; | 282 | $ENCODE = $this->_safeRegExp('$encode\\($count\\)'); |
283 | | 283 | |
284 | // $ascii: base for encoding | 284 | // $packed: the packed script |
285 | $ascii = min(count($keywords['sorted']), $this->_encoding); | 285 | $packed = "'" . $this->_escape($packed) . "'"; |
286 | if ($ascii == 0) $ascii = 1; | 286 | |
287 | | 287 | // $ascii: base for encoding |
288 | // $count: number of words contained in the script | 288 | $ascii = min(count($keywords['sorted']), $this->_encoding); |
289 | $count = count($keywords['sorted']); | 289 | if ($ascii == 0) $ascii = 1; |
290 | | 290 | |
291 | // $keywords: list of words contained in the script | 291 | // $count: number of words contained in the script |
292 | foreach ($keywords['protected'] as $i=>$value) { | 292 | $count = count($keywords['sorted']); |
293 | $keywords['sorted'][$i] = ''; | 293 | |
294 | } | 294 | // $keywords: list of words contained in the script |
295 | // convert from a string to an array | 295 | foreach ($keywords['protected'] as $i=>$value) { |
296 | ksort($keywords['sorted']); | 296 | $keywords['sorted'][$i] = ''; |
297 | $keywords = "'" . implode('|',$keywords['sorted']) . "'.split('|')"; | 297 | } |
298 | | 298 | // convert from a string to an array |
299 | $encode = ($this->_encoding > 62) ? '_encode95' : $this->_getEncoder($ascii); | 299 | ksort($keywords['sorted']); |
300 | $encode = $this->_getJSFunction($encode); | 300 | $keywords = "'" . implode('|',$keywords['sorted']) . "'.split('|')"; |
301 | $encode = preg_replace('/_encoding/','$ascii', $encode); | 301 | |
302 | $encode = preg_replace('/arguments\\.callee/','$encode', $encode); | 302 | $encode = ($this->_encoding > 62) ? '_encode95' : $this->_getEncoder($ascii); |
303 | $inline = '\\$count' . ($ascii > 10 ? '.toString(\\$ascii)' : ''); | 303 | $encode = $this->_getJSFunction($encode); |
304 | | 304 | $encode = preg_replace('/_encoding/','$ascii', $encode); |
305 | // $decode: code snippet to speed up decoding | 305 | $encode = preg_replace('/arguments\\.callee/','$encode', $encode); |
306 | if ($this->_fastDecode) { | 306 | $inline = '\\$count' . ($ascii > 10 ? '.toString(\\$ascii)' : ''); |
307 | // create the decoder | 307 | |
308 | $decode = $this->_getJSFunction('_decodeBody'); | 308 | // $decode: code snippet to speed up decoding |
309 | if ($this->_encoding > 62) | 309 | if ($this->_fastDecode) { |
310 | $decode = preg_replace('/\\\\w/', '[\\xa1-\\xff]', $decode); | 310 | // create the decoder |
311 | // perform the encoding inline for lower ascii values | 311 | $decode = $this->_getJSFunction('_decodeBody'); |
312 | elseif ($ascii < 36) | 312 | if ($this->_encoding > 62) |
313 | $decode = preg_replace($ENCODE, $inline, $decode); | 313 | $decode = preg_replace('/\\\\w/', '[\\xa1-\\xff]', $decode); |
314 | // special case: when $count==0 there are no keywords. I want to keep | 314 | // perform the encoding inline for lower ascii values |
315 | // the basic shape of the unpacking funcion so i'll frig the code... | 315 | elseif ($ascii < 36) |
316 | if ($count == 0) | 316 | $decode = preg_replace($ENCODE, $inline, $decode); |
317 | $decode = preg_replace($this->_safeRegExp('($count)\\s*=\\s*1'), '$1=0', $decode, 1); | 317 | // special case: when $count==0 there are no keywords. I want to keep |
318 | } | 318 | // the basic shape of the unpacking funcion so i'll frig the code... |
319 | | 319 | if ($count == 0) |
320 | // boot function | 320 | $decode = preg_replace($this->_safeRegExp('($count)\\s*=\\s*1'), '$1=0', $decode, 1); |
321 | $unpack = $this->_getJSFunction('_unpack'); | 321 | } |
322 | if ($this->_fastDecode) { | 322 | |
323 | // insert the decoder | 323 | // boot function |
324 | $this->buffer = $decode; | 324 | $unpack = $this->_getJSFunction('_unpack'); |
325 | $unpack = preg_replace_callback('/\\{/', array(&$this, '_insertFastDecode'), $unpack, 1); | 325 | if ($this->_fastDecode) { |
326 | } | 326 | // insert the decoder |
327 | $unpack = preg_replace('/"/', "'", $unpack); | 327 | $this->buffer = $decode; |
328 | if ($this->_encoding > 62) { // high-ascii | 328 | $unpack = preg_replace_callback('/\\{/', array(&$this, '_insertFastDecode'), $unpack, 1); |
329 | // get rid of the word-boundaries for regexp matches | 329 | } |
330 | $unpack = preg_replace('/\'\\\\\\\\b\'\s*\\+|\\+\s*\'\\\\\\\\b\'/', '', $unpack); | 330 | $unpack = preg_replace('/"/', "'", $unpack); |
331 | } | 331 | if ($this->_encoding > 62) { // high-ascii |
332 | if ($ascii > 36 || $this->_encoding > 62 || $this->_fastDecode) { | 332 | // get rid of the word-boundaries for regexp matches |
333 | // insert the encode function | 333 | $unpack = preg_replace('/\'\\\\\\\\b\'\s*\\+|\\+\s*\'\\\\\\\\b\'/', '', $unpack); |
334 | $this->buffer = $encode; | 334 | } |
335 | $unpack = preg_replace_callback('/\\{/', array(&$this, '_insertFastEncode'), $unpack, 1); | 335 | if ($ascii > 36 || $this->_encoding > 62 || $this->_fastDecode) { |
336 | } else { | 336 | // insert the encode function |
337 | // perform the encoding inline | 337 | $this->buffer = $encode; |
338 | $unpack = preg_replace($ENCODE, $inline, $unpack); | 338 | $unpack = preg_replace_callback('/\\{/', array(&$this, '_insertFastEncode'), $unpack, 1); |
339 | } | 339 | } else { |
340 | // pack the boot function too | 340 | // perform the encoding inline |
341 | $unpackPacker = new JavaScriptPacker($unpack, 0, false, true); | 341 | $unpack = preg_replace($ENCODE, $inline, $unpack); |
342 | $unpack = $unpackPacker->pack(); | 342 | } |
343 | | 343 | // pack the boot function too |
344 | // arguments | 344 | $unpackPacker = new JavaScriptPacker($unpack, 0, false, true); |
345 | $params = array($packed, $ascii, $count, $keywords); | 345 | $unpack = $unpackPacker->pack(); |
346 | if ($this->_fastDecode) { | 346 | |
347 | $params[] = 0; | 347 | // arguments |
348 | $params[] = '{}'; | 348 | $params = array($packed, $ascii, $count, $keywords); |
349 | } | 349 | if ($this->_fastDecode) { |
350 | $params = implode(',', $params); | 350 | $params[] = 0; |
351 | | 351 | $params[] = '{}'; |
352 | // the whole thing | 352 | } |
353 | return 'eval(' . $unpack . '(' . $params . "))\n"; | 353 | $params = implode(',', $params); |
354 | } | 354 | |
355 | | 355 | // the whole thing |
356 | private $buffer; | 356 | return 'eval(' . $unpack . '(' . $params . "))\n"; |
357 | private function _insertFastDecode($match) { | 357 | } |
358 | return '{' . $this->buffer . ';'; | 358 | |
359 | } | 359 | private $buffer; |
360 | private function _insertFastEncode($match) { | 360 | private function _insertFastDecode($match) { |
361 | return '{$encode=' . $this->buffer . ';'; | 361 | return '{' . $this->buffer . ';'; |
362 | } | 362 | } |
363 | | 363 | private function _insertFastEncode($match) { |
364 | // mmm.. ..which one do i need ?? | 364 | return '{$encode=' . $this->buffer . ';'; |
365 | private function _getEncoder($ascii) { | 365 | } |
366 | return $ascii > 10 ? $ascii > 36 ? $ascii > 62 ? | 366 | |
367 | '_encode95' : '_encode62' : '_encode36' : '_encode10'; | 367 | // mmm.. ..which one do i need ?? |
368 | } | 368 | private function _getEncoder($ascii) { |
369 | | 369 | return $ascii > 10 ? $ascii > 36 ? $ascii > 62 ? |
370 | // zero encoding | 370 | '_encode95' : '_encode62' : '_encode36' : '_encode10'; |
371 | // characters: 0123456789 | 371 | } |
372 | private function _encode10($charCode) { | 372 | |
373 | return $charCode; | 373 | // zero encoding |
374 | } | 374 | // characters: 0123456789 |
375 | | 375 | private function _encode10($charCode) { |
376 | // inherent base36 support | 376 | return $charCode; |
377 | // characters: 0123456789abcdefghijklmnopqrstuvwxyz | 377 | } |
378 | private function _encode36($charCode) { | 378 | |
379 | return base_convert($charCode, 10, 36); | 379 | // inherent base36 support |
380 | } | 380 | // characters: 0123456789abcdefghijklmnopqrstuvwxyz |
381 | | 381 | private function _encode36($charCode) { |
382 | // hitch a ride on base36 and add the upper case alpha characters | 382 | return base_convert($charCode, 10, 36); |
383 | // characters: 0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ | 383 | } |
384 | private function _encode62($charCode) { | 384 | |
385 | $res = ''; | 385 | // hitch a ride on base36 and add the upper case alpha characters |
386 | if ($charCode >= $this->_encoding) { | 386 | // characters: 0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ |
387 | $res = $this->_encode62((int)($charCode / $this->_encoding)); | 387 | private function _encode62($charCode) { |
388 | } | 388 | $res = ''; |
389 | $charCode = $charCode % $this->_encoding; | 389 | if ($charCode >= $this->_encoding) { |
390 | | 390 | $res = $this->_encode62((int)($charCode / $this->_encoding)); |
391 | if ($charCode > 35) | 391 | } |
392 | return $res . chr($charCode + 29); | 392 | $charCode = $charCode % $this->_encoding; |
393 | else | 393 | |
394 | return $res . base_convert($charCode, 10, 36); | 394 | if ($charCode > 35) |
395 | } | 395 | return $res . chr($charCode + 29); |
396 | | 396 | else |
397 | // use high-ascii values | 397 | return $res . base_convert($charCode, 10, 36); |
398 | // characters: ¡¢£¤¥¦§¨©ª«¬®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖרÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþ | 398 | } |
399 | private function _encode95($charCode) { | 399 | |
400 | $res = ''; | 400 | // use high-ascii values |
401 | if ($charCode >= $this->_encoding) | 401 | // characters: ¡¢£¤¥¦§¨©ª«¬®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖרÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþ |
402 | $res = $this->_encode95($charCode / $this->_encoding); | 402 | private function _encode95($charCode) { |
403 | | 403 | $res = ''; |
404 | return $res . chr(($charCode % $this->_encoding) + 161); | 404 | if ($charCode >= $this->_encoding) |
405 | } | 405 | $res = $this->_encode95($charCode / $this->_encoding); |
406 | | 406 | |
407 | private function _safeRegExp($string) { | 407 | return $res . chr(($charCode % $this->_encoding) + 161); |
408 | return '/'.preg_replace('/\$/', '\\\$', $string).'/'; | 408 | } |
409 | } | 409 | |
410 | | 410 | private function _safeRegExp($string) { |
411 | private function _encodePrivate($charCode) { | 411 | return '/'.preg_replace('/\$/', '\\\$', $string).'/'; |
412 | return "_" . $charCode; | 412 | } |
413 | } | 413 | |
414 | | 414 | private function _encodePrivate($charCode) { |
415 | // protect characters used by the parser | 415 | return "_" . $charCode; |
416 | private function _escape($script) { | 416 | } |
417 | return preg_replace('/([\\\\\'])/', '\\\$1', $script); | 417 | |
418 | } | 418 | // protect characters used by the parser |
419 | | 419 | private function _escape($script) { |
420 | // protect high-ascii characters already in the script | 420 | return preg_replace('/([\\\\\'])/', '\\\$1', $script); |
421 | private function _escape95($script) { | 421 | } |
422 | return preg_replace_callback( | 422 | |
423 | '/[\\xa1-\\xff]/', | 423 | // protect high-ascii characters already in the script |
424 | array(&$this, '_escape95Bis'), | 424 | private function _escape95($script) { |
425 | $script | 425 | return preg_replace_callback( |
426 | ); | 426 | '/[\\xa1-\\xff]/', |
427 | } | 427 | array(&$this, '_escape95Bis'), |
428 | private function _escape95Bis($match) { | 428 | $script |
429 | return '\x'.((string)dechex(ord($match))); | 429 | ); |
430 | } | 430 | } |
431 | | 431 | private function _escape95Bis($match) { |
432 | | 432 | return '\x'.((string)dechex(ord($match))); |
433 | private function _getJSFunction($aName) { | 433 | } |
434 | if (defined('self::JSFUNCTION'.$aName)) | 434 | |
435 | return constant('self::JSFUNCTION'.$aName); | 435 | |
436 | else | 436 | private function _getJSFunction($aName) { |
437 | return ''; | 437 | if (defined('self::JSFUNCTION'.$aName)) |
438 | } | 438 | return constant('self::JSFUNCTION'.$aName); |
439 | | 439 | else |
440 | // JavaScript Functions used. | 440 | return ''; |
441 | // Note : In Dean's version, these functions are converted | 441 | } |
442 | // with 'String(aFunctionName);'. | 442 | |
443 | // This internal conversion complete the original code, ex : | 443 | // JavaScript Functions used. |
444 | // 'while (aBool) anAction();' is converted to | 444 | // Note : In Dean's version, these functions are converted |
445 | // 'while (aBool) { anAction(); }'. | 445 | // with 'String(aFunctionName);'. |
446 | // The JavaScript functions below are corrected. | 446 | // This internal conversion complete the original code, ex : |
447 | | 447 | // 'while (aBool) anAction();' is converted to |
448 | // unpacking function - this is the boot strap function | 448 | // 'while (aBool) { anAction(); }'. |
449 | // data extracted from this packing routine is passed to | 449 | // The JavaScript functions below are corrected. |
450 | // this function when decoded in the target | 450 | |
451 | // NOTE ! : without the ';' final. | 451 | // unpacking function - this is the boot strap function |
452 | const JSFUNCTION_unpack = | 452 | // data extracted from this packing routine is passed to |
453 | | 453 | // this function when decoded in the target |
454 | 'function($packed, $ascii, $count, $keywords, $encode, $decode) { | 454 | // NOTE ! : without the ';' final. |
455 | while ($count--) { | 455 | const JSFUNCTION_unpack = |
456 | if ($keywords[$count]) { | 456 | |
457 | $packed = $packed.replace(new RegExp(\'\\\\b\' + $encode($count) + \'\\\\b\', \'g\'), $keywords[$count]); | 457 | 'function($packed, $ascii, $count, $keywords, $encode, $decode) { |
458 | } | 458 | while ($count--) { |
459 | } | 459 | if ($keywords[$count]) { |
460 | return $packed; | 460 | $packed = $packed.replace(new RegExp(\'\\\\b\' + $encode($count) + \'\\\\b\', \'g\'), $keywords[$count]); |
461 | }'; | 461 | } |
462 | /* | 462 | } |
463 | 'function($packed, $ascii, $count, $keywords, $encode, $decode) { | 463 | return $packed; |
464 | while ($count--) | 464 | }'; |
465 | if ($keywords[$count]) | 465 | /* |
466 | $packed = $packed.replace(new RegExp(\'\\\\b\' + $encode($count) + \'\\\\b\', \'g\'), $keywords[$count]); | 466 | 'function($packed, $ascii, $count, $keywords, $encode, $decode) { |
467 | return $packed; | 467 | while ($count--) |
468 | }'; | 468 | if ($keywords[$count]) |
469 | */ | 469 | $packed = $packed.replace(new RegExp(\'\\\\b\' + $encode($count) + \'\\\\b\', \'g\'), $keywords[$count]); |
470 | | 470 | return $packed; |
471 | // code-snippet inserted into the unpacker to speed up decoding | 471 | }'; |
472 | const JSFUNCTION_decodeBody = | 472 | */ |
473 | //_decode = function() { | 473 | |
474 | // does the browser support String.replace where the | 474 | // code-snippet inserted into the unpacker to speed up decoding |
475 | // replacement value is a function? | 475 | const JSFUNCTION_decodeBody = |
476 | | 476 | //_decode = function() { |
477 | ' if (!\'\'.replace(/^/, String)) { | 477 | // does the browser support String.replace where the |
478 | // decode all the values we need | 478 | // replacement value is a function? |
479 | while ($count--) { | 479 | |
480 | $decode[$encode($count)] = $keywords[$count] || $encode($count); | 480 | ' if (!\'\'.replace(/^/, String)) { |
481 | } | 481 | // decode all the values we need |
482 | // global replacement function | 482 | while ($count--) { |
483 | $keywords = [function ($encoded) {return $decode[$encoded]}]; | 483 | $decode[$encode($count)] = $keywords[$count] || $encode($count); |
484 | // generic match | 484 | } |
485 | $encode = function () {return \'\\\\w+\'}; | 485 | // global replacement function |
486 | // reset the loop counter - we are now doing a global replace | 486 | $keywords = [function ($encoded) {return $decode[$encoded]}]; |
487 | $count = 1; | 487 | // generic match |
488 | } | 488 | $encode = function () {return \'\\\\w+\'}; |
489 | '; | 489 | // reset the loop counter - we are now doing a global replace |
490 | //}; | 490 | $count = 1; |
491 | /* | 491 | } |
492 | ' if (!\'\'.replace(/^/, String)) { | 492 | '; |
493 | // decode all the values we need | 493 | //}; |
494 | while ($count--) $decode[$encode($count)] = $keywords[$count] || $encode($count); | 494 | /* |
495 | // global replacement function | 495 | ' if (!\'\'.replace(/^/, String)) { |
496 | $keywords = [function ($encoded) {return $decode[$encoded]}]; | 496 | // decode all the values we need |
497 | // generic match | 497 | while ($count--) $decode[$encode($count)] = $keywords[$count] || $encode($count); |
498 | $encode = function () {return\'\\\\w+\'}; | 498 | // global replacement function |
499 | // reset the loop counter - we are now doing a global replace | 499 | $keywords = [function ($encoded) {return $decode[$encoded]}]; |
500 | $count = 1; | 500 | // generic match |
501 | }'; | 501 | $encode = function () {return\'\\\\w+\'}; |
502 | */ | 502 | // reset the loop counter - we are now doing a global replace |
503 | | 503 | $count = 1; |
504 | // zero encoding | 504 | }'; |
505 | // characters: 0123456789 | 505 | */ |
506 | const JSFUNCTION_encode10 = | 506 | |
507 | 'function($charCode) { | 507 | // zero encoding |
508 | return $charCode; | 508 | // characters: 0123456789 |
509 | }';//;'; | 509 | const JSFUNCTION_encode10 = |
510 | | 510 | 'function($charCode) { |
511 | // inherent base36 support | 511 | return $charCode; |
512 | // characters: 0123456789abcdefghijklmnopqrstuvwxyz | 512 | }';//;'; |
513 | const JSFUNCTION_encode36 = | 513 | |
514 | 'function($charCode) { | 514 | // inherent base36 support |
515 | return $charCode.toString(36); | 515 | // characters: 0123456789abcdefghijklmnopqrstuvwxyz |
516 | }';//;'; | 516 | const JSFUNCTION_encode36 = |
517 | | 517 | 'function($charCode) { |
518 | // hitch a ride on base36 and add the upper case alpha characters | 518 | return $charCode.toString(36); |
519 | // characters: 0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ | 519 | }';//;'; |
520 | const JSFUNCTION_encode62 = | 520 | |
521 | 'function($charCode) { | 521 | // hitch a ride on base36 and add the upper case alpha characters |
522 | return ($charCode < _encoding ? \'\' : arguments.callee(parseInt($charCode / _encoding))) + | 522 | // characters: 0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ |
523 | (($charCode = $charCode % _encoding) > 35 ? String.fromCharCode($charCode + 29) : $charCode.toString(36)); | 523 | const JSFUNCTION_encode62 = |
524 | }'; | 524 | 'function($charCode) { |
525 | | 525 | return ($charCode < _encoding ? \'\' : arguments.callee(parseInt($charCode / _encoding))) + |
526 | // use high-ascii values | 526 | (($charCode = $charCode % _encoding) > 35 ? String.fromCharCode($charCode + 29) : $charCode.toString(36)); |
527 | // characters: ¡¢£¤¥¦§¨©ª«¬®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖרÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþ | 527 | }'; |
528 | const JSFUNCTION_encode95 = | 528 | |
529 | 'function($charCode) { | 529 | // use high-ascii values |
530 | return ($charCode < _encoding ? \'\' : arguments.callee($charCode / _encoding)) + | 530 | // characters: ¡¢£¤¥¦§¨©ª«¬®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖרÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþ |
531 | String.fromCharCode($charCode % _encoding + 161); | 531 | const JSFUNCTION_encode95 = |
532 | }'; | 532 | 'function($charCode) { |
533 | | 533 | return ($charCode < _encoding ? \'\' : arguments.callee($charCode / _encoding)) + |
534 | } | 534 | String.fromCharCode($charCode % _encoding + 161); |
535 | | 535 | }'; |
536 | | 536 | |
537 | class ParseMaster { | 537 | } |
538 | public $ignoreCase = false; | 538 | |
539 | public $escapeChar = ''; | 539 | |
540 | | 540 | class ParseMaster { |
541 | // constants | 541 | public $ignoreCase = false; |
542 | const EXPRESSION = 0; | 542 | public $escapeChar = ''; |
543 | const REPLACEMENT = 1; | 543 | |
544 | const LENGTH = 2; | 544 | // constants |
545 | | 545 | const EXPRESSION = 0; |
546 | // used to determine nesting levels | 546 | const REPLACEMENT = 1; |
547 | private $GROUPS = '/\\(/';//g | 547 | const LENGTH = 2; |
548 | private $SUB_REPLACE = '/\\$\\d/'; | 548 | |
549 | private $INDEXED = '/^\\$\\d+$/'; | 549 | // used to determine nesting levels |
550 | private $TRIM = '/([\'"])\\1\\.(.*)\\.\\1\\1$/'; | 550 | private $GROUPS = '/\\(/';//g |
551 | private $ESCAPE = '/\\\./';//g | 551 | private $SUB_REPLACE = '/\\$\\d/'; |
552 | private $QUOTE = '/\'/'; | 552 | private $INDEXED = '/^\\$\\d+$/'; |
553 | private $DELETED = '/\\x01[^\\x01]*\\x01/';//g | 553 | private $TRIM = '/([\'"])\\1\\.(.*)\\.\\1\\1$/'; |
554 | | 554 | private $ESCAPE = '/\\\./';//g |
555 | public function add($expression, $replacement = '') { | 555 | private $QUOTE = '/\'/'; |
556 | // count the number of sub-expressions | 556 | private $DELETED = '/\\x01[^\\x01]*\\x01/';//g |
557 | // - add one because each pattern is itself a sub-expression | 557 | |
558 | $length = 1 + preg_match_all($this->GROUPS, $this->_internalEscape((string)$expression), $out); | 558 | public function add($expression, $replacement = '') { |
559 | | 559 | // count the number of sub-expressions |
560 | // treat only strings $replacement | 560 | // - add one because each pattern is itself a sub-expression |
561 | if (is_string($replacement)) { | 561 | $length = 1 + preg_match_all($this->GROUPS, $this->_internalEscape((string)$expression), $out); |
562 | // does the pattern deal with sub-expressions? | 562 | |
563 | if (preg_match($this->SUB_REPLACE, $replacement)) { | 563 | // treat only strings $replacement |
564 | // a simple lookup? (e.g. "$2") | 564 | if (is_string($replacement)) { |
565 | if (preg_match($this->INDEXED, $replacement)) { | 565 | // does the pattern deal with sub-expressions? |
566 | // store the index (used for fast retrieval of matched strings) | 566 | if (preg_match($this->SUB_REPLACE, $replacement)) { |
567 | $replacement = (int)(substr($replacement, 1)) - 1; | 567 | // a simple lookup? (e.g. "$2") |
568 | } else { // a complicated lookup (e.g. "Hello $2 $1") | 568 | if (preg_match($this->INDEXED, $replacement)) { |
569 | // build a function to do the lookup | 569 | // store the index (used for fast retrieval of matched strings) |
570 | $quote = preg_match($this->QUOTE, $this->_internalEscape($replacement)) | 570 | $replacement = (int)(substr($replacement, 1)) - 1; |
571 | ? '"' : "'"; | 571 | } else { // a complicated lookup (e.g. "Hello $2 $1") |
572 | $replacement = array( | 572 | // build a function to do the lookup |
573 | 'fn' => '_backReferences', | 573 | $quote = preg_match($this->QUOTE, $this->_internalEscape($replacement)) |
574 | 'data' => array( | 574 | ? '"' : "'"; |
575 | 'replacement' => $replacement, | 575 | $replacement = array( |
576 | 'length' => $length, | 576 | 'fn' => '_backReferences', |
577 | 'quote' => $quote | 577 | 'data' => array( |
578 | ) | 578 | 'replacement' => $replacement, |
579 | ); | 579 | 'length' => $length, |
580 | } | 580 | 'quote' => $quote |
581 | } | 581 | ) |
582 | } | 582 | ); |
583 | // pass the modified arguments | 583 | } |
584 | if (!empty($expression)) $this->_add($expression, $replacement, $length); | 584 | } |
585 | else $this->_add('/^$/', $replacement, $length); | 585 | } |
586 | } | 586 | // pass the modified arguments |
587 | | 587 | if (!empty($expression)) $this->_add($expression, $replacement, $length); |
588 | public function exec($string) { | 588 | else $this->_add('/^$/', $replacement, $length); |
589 | // execute the global replacement | 589 | } |
590 | $this->_escaped = array(); | 590 | |
591 | | 591 | public function exec($string) { |
592 | // simulate the _patterns.toSTring of Dean | 592 | // execute the global replacement |
593 | $regexp = '/'; | 593 | $this->_escaped = array(); |
594 | foreach ($this->_patterns as $reg) { | 594 | |
595 | $regexp .= '(' . substr($reg[self::EXPRESSION], 1, -1) . ')|'; | 595 | // simulate the _patterns.toSTring of Dean |
596 | } | 596 | $regexp = '/'; |
597 | $regexp = substr($regexp, 0, -1) . '/'; | 597 | foreach ($this->_patterns as $reg) { |
598 | $regexp .= ($this->ignoreCase) ? 'i' : ''; | 598 | $regexp .= '(' . substr($reg[self::EXPRESSION], 1, -1) . ')|'; |
599 | | 599 | } |
600 | $string = $this->_escape($string, $this->escapeChar); | 600 | $regexp = substr($regexp, 0, -1) . '/'; |
601 | $string = preg_replace_callback( | 601 | $regexp .= ($this->ignoreCase) ? 'i' : ''; |
602 | $regexp, | 602 | |
603 | array( | 603 | $string = $this->_escape($string, $this->escapeChar); |
604 | &$this, | 604 | $string = preg_replace_callback( |
605 | '_replacement' | 605 | $regexp, |
606 | ), | 606 | array( |
607 | $string | 607 | &$this, |
608 | ); | 608 | '_replacement' |
609 | $string = $this->_unescape($string, $this->escapeChar); | 609 | ), |
610 | | 610 | $string |
611 | return preg_replace($this->DELETED, '', $string); | 611 | ); |
612 | } | 612 | $string = $this->_unescape($string, $this->escapeChar); |
613 | | 613 | |
614 | public function reset() { | 614 | return preg_replace($this->DELETED, '', $string); |
615 | // clear the patterns collection so that this object may be re-used | 615 | } |
616 | $this->_patterns = array(); | 616 | |
617 | } | 617 | public function reset() { |
618 | | 618 | // clear the patterns collection so that this object may be re-used |
619 | // private | 619 | $this->_patterns = array(); |
620 | private $_escaped = array(); // escaped characters | 620 | } |
621 | private $_patterns = array(); // patterns stored by index | 621 | |
622 | | 622 | // private |
623 | // create and add a new pattern to the patterns collection | 623 | private $_escaped = array(); // escaped characters |
624 | private function _add() { | 624 | private $_patterns = array(); // patterns stored by index |
625 | $arguments = func_get_args(); | 625 | |
626 | $this->_patterns[] = $arguments; | 626 | // create and add a new pattern to the patterns collection |
627 | } | 627 | private function _add() { |
628 | | 628 | $arguments = func_get_args(); |
629 | // this is the global replace function (it's quite complicated) | 629 | $this->_patterns[] = $arguments; |
630 | private function _replacement($arguments) { | 630 | } |
631 | if (empty($arguments)) return ''; | 631 | |
632 | | 632 | // this is the global replace function (it's quite complicated) |
633 | $i = 1; $j = 0; | 633 | private function _replacement($arguments) { |
634 | // loop through the patterns | 634 | if (empty($arguments)) return ''; |
635 | while (isset($this->_patterns[$j])) { | 635 | |
636 | $pattern = $this->_patterns[$j++]; | 636 | $i = 1; $j = 0; |
637 | // do we have a result? | 637 | // loop through the patterns |
638 | if (isset($arguments[$i]) && ($arguments[$i] != '')) { | 638 | while (isset($this->_patterns[$j])) { |
639 | $replacement = $pattern[self::REPLACEMENT]; | 639 | $pattern = $this->_patterns[$j++]; |
640 | | 640 | // do we have a result? |
641 | if (is_array($replacement) && isset($replacement['fn'])) { | 641 | if (isset($arguments[$i]) && ($arguments[$i] != '')) { |
642 | | 642 | $replacement = $pattern[self::REPLACEMENT]; |
643 | if (isset($replacement['data'])) $this->buffer = $replacement['data']; | 643 | |
644 | return call_user_func(array(&$this, $replacement['fn']), $arguments, $i); | 644 | if (is_array($replacement) && isset($replacement['fn'])) { |
645 | | 645 | |
646 | } elseif (is_int($replacement)) { | 646 | if (isset($replacement['data'])) $this->buffer = $replacement['data']; |
647 | return $arguments[$replacement + $i]; | 647 | return call_user_func(array(&$this, $replacement['fn']), $arguments, $i); |
648 | | 648 | |
649 | } | 649 | } elseif (is_int($replacement)) { |
650 | $delete = ($this->escapeChar == '' || | 650 | return $arguments[$replacement + $i]; |
651 | strpos($arguments[$i], $this->escapeChar) === false) | 651 | |
652 | ? '' : "\x01" . $arguments[$i] . "\x01"; | 652 | } |
653 | return $delete . $replacement; | 653 | $delete = ($this->escapeChar == '' || |
654 | | 654 | strpos($arguments[$i], $this->escapeChar) === false) |
655 | // skip over references to sub-expressions | 655 | ? '' : "\x01" . $arguments[$i] . "\x01"; |
656 | } else { | 656 | return $delete . $replacement; |
657 | $i += $pattern[self::LENGTH]; | 657 | |
658 | } | 658 | // skip over references to sub-expressions |
659 | } | 659 | } else { |
660 | } | 660 | $i += $pattern[self::LENGTH]; |
661 | | 661 | } |
662 | private function _backReferences($match, $offset) { | 662 | } |
663 | $replacement = $this->buffer['replacement']; | 663 | } |
664 | $quote = $this->buffer['quote']; | 664 | |
665 | $i = $this->buffer['length']; | 665 | private function _backReferences($match, $offset) { |
666 | while ($i) { | 666 | $replacement = $this->buffer['replacement']; |
667 | $replacement = str_replace('$'.$i--, $match[$offset + $i], $replacement); | 667 | $quote = $this->buffer['quote']; |
668 | } | 668 | $i = $this->buffer['length']; |
669 | return $replacement; | 669 | while ($i) { |
670 | } | 670 | $replacement = str_replace('$'.$i--, $match[$offset + $i], $replacement); |
671 | | 671 | } |
672 | private function _replace_name($match, $offset){ | 672 | return $replacement; |
673 | $length = strlen($match[$offset + 2]); | 673 | } |
674 | $start = $length - max($length - strlen($match[$offset + 3]), 0); | 674 | |
675 | return substr($match[$offset + 1], $start, $length) . $match[$offset + 4]; | 675 | private function _replace_name($match, $offset){ |
676 | } | 676 | $length = strlen($match[$offset + 2]); |
677 | | 677 | $start = $length - max($length - strlen($match[$offset + 3]), 0); |
678 | private function _replace_encoded($match, $offset) { | 678 | return substr($match[$offset + 1], $start, $length) . $match[$offset + 4]; |
679 | return $this->buffer[$match[$offset]]; | 679 | } |
680 | } | 680 | |
681 | | 681 | private function _replace_encoded($match, $offset) { |
682 | | 682 | return $this->buffer[$match[$offset]]; |
683 | // php : we cannot pass additional data to preg_replace_callback, | 683 | } |
684 | // and we cannot use &$this in create_function, so let's go to lower level | 684 | |
685 | private $buffer; | 685 | |
686 | | 686 | // php : we cannot pass additional data to preg_replace_callback, |
687 | // encode escaped characters | 687 | // and we cannot use &$this in create_function, so let's go to lower level |
688 | private function _escape($string, $escapeChar) { | 688 | private $buffer; |
689 | if ($escapeChar) { | 689 | |
690 | $this->buffer = $escapeChar; | 690 | // encode escaped characters |
691 | return preg_replace_callback( | 691 | private function _escape($string, $escapeChar) { |
692 | '/\\' . $escapeChar . '(.)' .'/', | 692 | if ($escapeChar) { |
693 | array(&$this, '_escapeBis'), | 693 | $this->buffer = $escapeChar; |
694 | $string | 694 | return preg_replace_callback( |
695 | ); | 695 | '/\\' . $escapeChar . '(.)' .'/', |
696 | | 696 | array(&$this, '_escapeBis'), |
697 | } else { | 697 | $string |
698 | return $string; | 698 | ); |
699 | } | 699 | |
700 | } | 700 | } else { |
701 | private function _escapeBis($match) { | 701 | return $string; |
702 | $this->_escaped[] = $match[1]; | 702 | } |
703 | return $this->buffer; | 703 | } |
704 | } | 704 | private function _escapeBis($match) { |
705 | | 705 | $this->_escaped[] = $match[1]; |
706 | // decode escaped characters | 706 | return $this->buffer; |
707 | private function _unescape($string, $escapeChar) { | 707 | } |
708 | if ($escapeChar) { | 708 | |
709 | $regexp = '/'.'\\'.$escapeChar.'/'; | 709 | // decode escaped characters |
710 | $this->buffer = array('escapeChar'=> $escapeChar, 'i' => 0); | 710 | private function _unescape($string, $escapeChar) { |
711 | return preg_replace_callback | 711 | if ($escapeChar) { |
712 | ( | 712 | $regexp = '/'.'\\'.$escapeChar.'/'; |
713 | $regexp, | 713 | $this->buffer = array('escapeChar'=> $escapeChar, 'i' => 0); |
714 | array(&$this, '_unescapeBis'), | 714 | return preg_replace_callback |
715 | $string | 715 | ( |
716 | ); | 716 | $regexp, |
717 | | 717 | array(&$this, '_unescapeBis'), |
718 | } else { | 718 | $string |
719 | return $string; | 719 | ); |
720 | } | 720 | |
721 | } | 721 | } else { |
722 | private function _unescapeBis() { | 722 | return $string; |
723 | if (!empty($this->_escaped[$this->buffer['i']])) { | 723 | } |
724 | $temp = $this->_escaped[$this->buffer['i']]; | 724 | } |
725 | } else { | 725 | private function _unescapeBis() { |
726 | $temp = ''; | 726 | if (isset($this->_escaped[$this->buffer['i']]) |
727 | } | 727 | && $this->_escaped[$this->buffer['i']] != '') |
728 | $this->buffer['i']++; | 728 | { |
729 | return $this->buffer['escapeChar'] . $temp; | 729 | $temp = $this->_escaped[$this->buffer['i']]; |
730 | } | 730 | } else { |
731 | | 731 | $temp = ''; |
732 | private function _internalEscape($string) { | 732 | } |
733 | return preg_replace($this->ESCAPE, '', $string); | 733 | $this->buffer['i']++; |
734 | } | 734 | return $this->buffer['escapeChar'] . $temp; |
735 | } | 735 | } |
736 | ?> | 736 | |
| | 737 | private function _internalEscape($string) { |
| | 738 | return preg_replace($this->ESCAPE, '', $string); |
| | 739 | } |
| | 740 | } |
| | 741 | ?> |