1 : <?php
2 : /**
3 : * Roman de Renart
4 : *
5 : * PHP version 5
6 : *
7 : * @category Rdr
8 : * @package Edit
9 : * @author Michel Corne <mcorne@yahoo.com>
10 : * @copyright 2010 Michel Corne
11 : * @license http://www.opensource.org/licenses/bsd-license.php The BSD License
12 : * @link http://roman-de-renart.blogspot.com/
13 : * @version SVN: $Id$
14 : */
15 :
16 : /**
17 : * Base functions
18 : *
19 : * @category Rdr
20 : * @package Edit
21 : * @author Michel Corne <mcorne@yahoo.com>
22 : * @copyright 2010 Michel Corne
23 : * @license http://www.opensource.org/licenses/bsd-license.php The BSD License
24 : */
25 :
26 : class Base
27 : {
28 : /**
29 : * The string format to display two strings
30 : */
31 : const ERR_MISSING_PROPERTY = 'missing property';
32 :
33 : /**
34 : * The string format to display two strings
35 : */
36 : const STRING_DIFF_FMT = "%s\n%s";
37 :
38 : /**
39 : * The replacement string string for a missing value
40 : */
41 : const MISSING_VALUE = '*** MISSING VALUE ***';
42 :
43 : /**
44 : * The replacement string for an empty value
45 : */
46 : const EMPTY_VALUE = '*** EMPTY VALUE ***';
47 :
48 : /**
49 : * Aborts the current process and throws an exception
50 : *
51 : * @param string $message the message to throw
52 : * @return void
53 : * @throws Exception throws an exception as instructed by the calling method
54 : */
55 : public function abort($message)
56 : {
57 40 : error_reporting(E_ERROR);
58 40 : throw new Exception("ERROR: $message");
59 : }
60 :
61 : /**
62 : * Adds a non-breaking space to some puncuation characters
63 : *
64 : * @param string $string the input string
65 : * @return string the string with non-breaking spaces
66 : */
67 : public function addNbsp($string)
68 : {
69 4 : $string = str_replace(' :', ' :', $string);
70 4 : $string = str_replace(' !', ' !', $string);
71 4 : $string = str_replace(' ?', ' ?', $string);
72 4 : $string = str_replace(' »', ' »', $string);
73 4 : $string = str_replace('« ', '« ', $string);
74 :
75 4 : return $string;
76 : }
77 :
78 : /**
79 : * Combines keys and values of each array of a list of arrays
80 : *
81 : * @param array $keys the list of keys
82 : * @param array $arrays the list of arrays
83 : * @return array the list of arrays combined with keys
84 : */
85 : public function arraysCombine($keys, $arrays)
86 : {
87 5 : $combined = array();
88 :
89 5 : foreach($arrays as $values) {
90 5 : $combined[] = array_combine($keys, $values);
91 5 : }
92 :
93 5 : return $combined;
94 : }
95 :
96 : /**
97 : * Compares two arrays and returns the difference into a string
98 : *
99 : * The difference is returned for each array key if any.
100 : * A missing key in any of the arrays is reported as *missing*.
101 : * An empty value is reported explicitly as *empty*
102 : *
103 : * @param array $array1 the first array
104 : * @param array $array2 the second array
105 : * @param string $separator the separator between the key and the difference
106 : * @return string the difference between the two arrays
107 : */
108 : public function arrayDiffToString($array1, $array2, $separator = null)
109 : {
110 4 : is_null($separator) and $separator = " =>\n";
111 :
112 4 : settype($array1, 'array');
113 4 : settype($array2, 'array');
114 :
115 4 : $difference = array();
116 :
117 4 : foreach(array_keys($array1 + $array2) as $key) {
118 : // reports the key as missing if applicable
119 4 : $value1 = isset($array1[$key])? $array1[$key] : self::MISSING_VALUE;
120 4 : $value2 = isset($array2[$key])? $array2[$key] : self::MISSING_VALUE;
121 :
122 : // reports the value as empty if applicable
123 4 : $value1 or $value1 === 0 or $value1 = self::EMPTY_VALUE;
124 4 : $value2 or $value2 === 0 or $value2 = self::EMPTY_VALUE;
125 :
126 4 : if ($value1 !== $value2) {
127 : // converts the values to string if arrays
128 4 : is_array($value1) and $value1 = $this->arrayToString($value1, true, null, "\n");
129 4 : is_array($value2) and $value2 = $this->arrayToString($value2, true, null, "\n");
130 : // the key is assumed to be a line number if numeric, increments the key
131 4 : $reference = is_numeric($key)? ($key + 1) : $key;
132 : // converts the difference to a string
133 4 : $difference[$reference] = $this->stringDiffToString($value1, $value2);
134 4 : }
135 4 : }
136 :
137 4 : return $this->arrayToString($difference, true, $separator);
138 : }
139 :
140 : /**
141 : * Applies a callback method to each elements of an array
142 : *
143 : * @param string $callback the callback method
144 : * @param array $array the array to run through the callback method
145 : * @return array an array containing all the elements processed by the callback function
146 : */
147 : public function arrayMap($callback, $array)
148 : {
149 26 : is_array($callback) or $callback = array($this, $callback);
150 :
151 26 : return array_map($callback, $array);
152 : }
153 :
154 : /**
155 : * Applies a callback method to the elements of an array matching a list of keys
156 : *
157 : * @param string $callback the callback method
158 : * @param array $array the array to run through the callback method
159 : * @param array $keys the list of keys
160 : * @return array an array containing all the elements
161 : * including those processed by the callback function
162 : */
163 : public function arrayMapKeys($callback, $array, $keys)
164 : {
165 :
166 : // extracts the elements matching the list of keys
167 6 : $intersect = array_intersect_key($array, array_flip($keys));
168 : // applies the callback method to those elements
169 6 : $intersect = $this->arrayMap($callback, $intersect);
170 :
171 : // merges the processed element with the original array
172 6 : return array_merge($array, $intersect);
173 : }
174 :
175 : /**
176 : * Applies a callback method to each elements of two arrays
177 : *
178 : * @param string $callback the callback method
179 : * @param array $array1 the first array to run through the callback method
180 : * @param array $array2 the second array to run through the callback method
181 : * @return array an array containing all the elements processed by the callback function
182 : */
183 : public function arraysMap($callback, $array1, $array2)
184 : {
185 2 : is_array($callback) or $callback = array($this, $callback);
186 : // extracts the keys of the first array
187 : // the second array is expected to have the exact same keys
188 2 : $keys = array_keys($array1);
189 : // applies the callback method to both arrays
190 2 : $values = array_map($callback, $array1, $array2);
191 :
192 : // combines the keys and the processed values
193 2 : return array_combine($keys, $values);
194 : }
195 :
196 : /**
197 : * Shifts the first value off each array of a list of arrays
198 : *
199 : * @param array $arrays the list of arrays
200 : * @return array the arrays without their first element
201 : */
202 : public function arraysShift($arrays)
203 : {
204 5 : foreach($arrays as &$array) {
205 5 : array_shift($array);
206 5 : }
207 :
208 5 : return $arrays;
209 : }
210 :
211 : /**
212 : * Converts an array to a string
213 : *
214 : * Empty elements are ignored.
215 : *
216 : * @param array $array the array to convert to a string
217 : * @param boolean $withKeys adds the keys if true, displays values if false
218 : * @param string $separator the key-value separator
219 : * @param string $glue the string glued between each value
220 : * @return string a string containing the array values or key-values
221 : */
222 : public function arrayToString($array, $withKeys = false, $separator = null, $glue = null)
223 : {
224 54 : is_null($separator) and $separator = ' => ';
225 54 : is_null($glue) and $glue = "\n\n";
226 :
227 54 : $array = array_filter($array);
228 :
229 54 : $withKeys and array_walk($array, array($this, 'keyAndValueToString'), $separator);
230 :
231 54 : return implode($glue, $array);
232 : }
233 :
234 : /**
235 : * Completes a pattern to use in a preg_match() like function
236 : *
237 : * The pattern is expected to be a string typically containing an HTML template
238 : * using %s in place of each item to match.
239 : * The template would typically be used by an sprintf() function.
240 : *
241 : * @param string $pattern the pattern
242 : * @param mixed $replace the string or array to replace all the %s in the pattern,
243 : * applying str_replace('%s', $replace, $pattern) if an array
244 : * @param boolean $prefixSpecials characters that are prep special characters are prefixed if true,
245 : * they are not prefixed if false
246 : * @return string the completed pattern
247 : */
248 : public function completePattern($pattern, $replace = null, $prefixSpecials = true)
249 : {
250 16 : is_null($replace) and $replace = '(.*?)';
251 :
252 : // normalizes the pattern
253 16 : $pattern = $this->normalizeHtml($pattern);
254 : // prefixes preg special characters
255 16 : $prefixSpecials and $pattern = preg_replace('~([[\\\^$.|?*+()\\]])~', '\\\$1', $pattern);
256 :
257 : // replaces all the %s
258 16 : if (is_array($replace)) {
259 1 : $pattern = vsprintf($pattern, $replace);
260 1 : } else {
261 16 : $pattern = str_replace('%s', $replace, $pattern);
262 : }
263 :
264 16 : return trim($pattern);
265 : }
266 :
267 : /**
268 : * Decodes a UTF-8 string into the output encoding
269 : *
270 : * Only applied in CGI mode.
271 : * Uses CP850 for MS-DOS.
272 : *
273 : * @param string $string the string to decode
274 : * @return string the decoded string
275 : */
276 : public function decodeString($string)
277 : {
278 2 : if ($this->isCgi()) {
279 2 : $encoding = $this->isWindows()? 'CP850' : iconv_get_encoding('output_encoding');
280 2 : $string = iconv('UTF-8', $encoding, $string);
281 2 : }
282 :
283 2 : return $string;
284 : }
285 :
286 : /**
287 : * Extracts the text that matches a full pattern
288 : *
289 : * @param string $pattern the pattern
290 : * @param string $subject the input string
291 : * @param string $message the error message to throw if there is no match
292 : * @return string the text that matched the full pattern
293 : * @throws Exception an exception is thrown by abort() if there is no match
294 : */
295 : public function extract($pattern, $subject, $message)
296 : {
297 3 : preg_match("~$pattern~sm", $subject, $match) or $this->abort($message);
298 :
299 3 : return $match[0];
300 : }
301 :
302 : /**
303 : * Extracts the strings matched by the first parenthesized subpattern
304 : *
305 : * @param string $pattern the pattern
306 : * @param string $subject the input string
307 : * @param string $message the error message to throw if there is no match
308 : * @return array the strings matched by the first parenthesized subpattern
309 : * @throws Exception an exception is thrown by abort() if there is no match
310 : */
311 : public function extractAll($pattern, $subject, $message)
312 : {
313 6 : preg_match_all("~$pattern~sm", $subject, $matches) or $this->abort($message);
314 :
315 6 : return $matches[1];
316 : }
317 :
318 : /**
319 : * Finds whether in CGI mode.
320 : *
321 : * @return bool true if CGI mode, false otherwise.
322 : */
323 : public function isCgi()
324 : {
325 4 : return PHP_SAPI == 'cli';
326 : }
327 :
328 : /**
329 : * Finds whether the OS is windows.
330 : *
331 : * @return bool true if windows, false otherwise.
332 : */
333 : public function isWindows()
334 : {
335 3 : return stripos(PHP_OS, 'win') !== false;
336 : }
337 :
338 : /**
339 : * Implodes an array key-value to a string
340 : *
341 : * Typically used as callback by an array_walk() like function.
342 : * Use only with string type value.
343 : *
344 : * @param string &$value the array element value
345 : * @param string $key the array element key
346 : * @param string $separator the key-value separator
347 : * @return void
348 : */
349 : public function keyAndValueToString(&$value, $key, $separator)
350 : {
351 54 : $value = "$key$separator$value";
352 54 : }
353 :
354 : /**
355 : * Searches subject for a match to the regular expression given in pattern.
356 : *
357 : * @param string $pattern the pattern
358 : * @param string $subject the input string
359 : * @param string $message the error message to throw if there is no match
360 : * @param array $keys the keys to combine to the matching strings
361 : * @param boolean $noShift keeps the full pattern match if true, removes it if false
362 : * @return array the list of strings matching each captured parenthesized subpattern
363 : * @throws Exception an exception is thrown by abort() if there is no match
364 : */
365 : public function match($pattern, $subject, $message, $keys = null, $noShift = false)
366 : {
367 11 : preg_match("~$pattern~sm", $subject, $match) or $this->abort($message);
368 11 : $noShift or array_shift($match);
369 :
370 11 : if ($keys) {
371 : // removes the extra keys
372 10 : $match = array_slice($match, 0, count($keys));
373 : // combines keys and values
374 10 : $match = array_combine($keys, $match);
375 10 : }
376 :
377 11 : return $match;
378 : }
379 :
380 : /**
381 : * Searches subject for all matches to the regular expression given in pattern
382 : *
383 : * @param string $pattern the pattern
384 : * @param string $subject the input string
385 : * @param string $message the error message to throw if there is no match
386 : * @param array $keys the keys to combine to the matching strings
387 : * @param boolean $noShift keeps the full pattern match in each set if true, removes it if false
388 : * @return array the sets of matched patterns, the first set of matches, then the second set, etc...
389 : * @throws Exception an exception is thrown by abort() if there is no match
390 : */
391 : public function matchAll($pattern, $subject, $message, $keys = null, $noShift = false)
392 : {
393 4 : preg_match_all("~$pattern~sm", $subject, $matches, PREG_SET_ORDER) or $this->abort($message);
394 :
395 4 : $noShift or $matches = $this->arraysShift($matches);
396 4 : $keys and $matches = $this->arraysCombine($keys, $matches);
397 :
398 4 : return $matches;
399 : }
400 :
401 : /**
402 : * Compares two arrays or strings and returns the difference into a string
403 : *
404 : * @param mixed $mixed1 the first array or string
405 : * @param mixed $mixed2 the second array or string
406 : * @param string $separator the key-value separator, only used for arrays
407 : * @return string a string with the difference between the two arrays or strings
408 : */
409 : public function mixedDiffToString($mixed1, $mixed2 , $separator = null)
410 : {
411 2 : return (is_array($mixed1) or is_array($mixed2))?
412 2 : $this->arrayDiffToString($mixed1, $mixed2, $separator) :
413 2 : $this->stringDiffToString($mixed1, $mixed2);
414 : }
415 :
416 : /**
417 : * Normalizes an HTML string
418 : *
419 : * Removes line-breaks and extra-spaces.
420 : *
421 : * @param string $string the input string
422 : * @return string the normalized string
423 : */
424 : public function normalizeHtml($string)
425 : {
426 : // removes spaces between tags
427 18 : $string = preg_replace('~>\s+~', '>', $string);
428 18 : $string = preg_replace('~\s+<~', '<', $string);
429 : // removes extra spaces
430 : // note: do not use \s that screws up the "à" character !
431 18 : $string = preg_replace('~[\n\t ]+~', ' ', $string);
432 :
433 18 : return trim($string);
434 : }
435 :
436 : /**
437 : * Replaces non-breaking space entities with a space character
438 : *
439 : * @param string $string the input string
440 : * @return string a string with changed into spaces
441 : */
442 : public function removeNbsp($string)
443 : {
444 11 : return str_replace(' ', ' ', $string);
445 : }
446 :
447 : /**
448 : * Compares two strings and returns the difference into a string
449 : *
450 : * @param string $string1 the first string
451 : * @param string $string2 the second string
452 : * @return string the two strings if they are different, an empty string otherwise
453 : */
454 : public function stringDiffToString($string1, $string2)
455 : {
456 5 : return $string1 !== $string2? sprintf(self::STRING_DIFF_FMT, $string1, $string2) : '';
457 : }
458 :
459 : /**
460 : * Tidy an HTML string
461 : *
462 : * Normalizes the string.
463 : * Replaces non-breaking space entities with a space character.
464 : * Replaces each HTML entity with its UFT-8 character.
465 : *
466 : * @param string $string the input string
467 : * @return string the decoded string
468 : */
469 : public function tidyString($string)
470 : {
471 10 : $string = $this->removeNbsp($string);
472 10 : $string = $this->normalizeHtml($string);
473 10 : $string = html_entity_decode($string, ENT_QUOTES, 'UTF-8');
474 :
475 10 : return $string;
476 : }
|