1: <?php
2:
3: 4: 5: 6: 7: 8: 9: 10:
11:
12: namespace Nette\Utils;
13:
14: use Nette,
15: Nette\Diagnostics\Debugger;
16:
17:
18:
19: 20: 21: 22: 23:
24: class Strings
25: {
26:
27: 28: 29:
30: final public function __construct()
31: {
32: throw new Nette\StaticClassException;
33: }
34:
35:
36:
37: 38: 39: 40: 41: 42:
43: public static function checkEncoding($s, $encoding = 'UTF-8')
44: {
45: return $s === self::fixEncoding($s, $encoding);
46: }
47:
48:
49:
50: 51: 52: 53: 54: 55:
56: public static function fixEncoding($s, $encoding = 'UTF-8')
57: {
58:
59: $s = @iconv('UTF-16', $encoding . '//IGNORE', iconv($encoding, 'UTF-16//IGNORE', $s));
60: return str_replace("\xEF\xBB\xBF", '', $s);
61: }
62:
63:
64:
65: 66: 67: 68: 69: 70:
71: public static function chr($code, $encoding = 'UTF-8')
72: {
73: return iconv('UTF-32BE', $encoding . '//IGNORE', pack('N', $code));
74: }
75:
76:
77:
78: 79: 80: 81: 82: 83:
84: public static function startsWith($haystack, $needle)
85: {
86: return strncmp($haystack, $needle, strlen($needle)) === 0;
87: }
88:
89:
90:
91: 92: 93: 94: 95: 96:
97: public static function endsWith($haystack, $needle)
98: {
99: return strlen($needle) === 0 || substr($haystack, -strlen($needle)) === $needle;
100: }
101:
102:
103:
104: 105: 106: 107: 108: 109:
110: public static function contains($haystack, $needle)
111: {
112: return strpos($haystack, $needle) !== FALSE;
113: }
114:
115:
116:
117: 118: 119: 120: 121: 122: 123:
124: public static function substring($s, $start, $length = NULL)
125: {
126: if ($length === NULL) {
127: $length = self::length($s);
128: }
129: return function_exists('mb_substr') ? mb_substr($s, $start, $length, 'UTF-8') : iconv_substr($s, $start, $length, 'UTF-8');
130: }
131:
132:
133:
134: 135: 136: 137: 138:
139: public static function normalize($s)
140: {
141:
142: $s = str_replace("\r\n", "\n", $s);
143: $s = strtr($s, "\r", "\n");
144:
145:
146: $s = preg_replace('#[\x00-\x08\x0B-\x1F\x7F]+#', '', $s);
147:
148:
149: $s = preg_replace("#[\t ]+$#m", '', $s);
150:
151:
152: $s = trim($s, "\n");
153:
154: return $s;
155: }
156:
157:
158:
159: 160: 161: 162: 163:
164: public static function toAscii($s)
165: {
166: $s = preg_replace('#[^\x09\x0A\x0D\x20-\x7E\xA0-\x{2FF}\x{370}-\x{10FFFF}]#u', '', $s);
167: $s = strtr($s, '`\'"^~', "\x01\x02\x03\x04\x05");
168: if (ICONV_IMPL === 'glibc') {
169: $s = @iconv('UTF-8', 'WINDOWS-1250//TRANSLIT', $s);
170: $s = strtr($s, "\xa5\xa3\xbc\x8c\xa7\x8a\xaa\x8d\x8f\x8e\xaf\xb9\xb3\xbe\x9c\x9a\xba\x9d\x9f\x9e"
171: . "\xbf\xc0\xc1\xc2\xc3\xc4\xc5\xc6\xc7\xc8\xc9\xca\xcb\xcc\xcd\xce\xcf\xd0\xd1\xd2\xd3"
172: . "\xd4\xd5\xd6\xd7\xd8\xd9\xda\xdb\xdc\xdd\xde\xdf\xe0\xe1\xe2\xe3\xe4\xe5\xe6\xe7\xe8"
173: . "\xe9\xea\xeb\xec\xed\xee\xef\xf0\xf1\xf2\xf3\xf4\xf5\xf6\xf8\xf9\xfa\xfb\xfc\xfd\xfe",
174: "ALLSSSSTZZZallssstzzzRAAAALCCCEEEEIIDDNNOOOOxRUUUUYTsraaaalccceeeeiiddnnooooruuuuyt");
175: } else {
176: $s = @iconv('UTF-8', 'ASCII//TRANSLIT', $s);
177: }
178: $s = str_replace(array('`', "'", '"', '^', '~'), '', $s);
179: return strtr($s, "\x01\x02\x03\x04\x05", '`\'"^~');
180: }
181:
182:
183:
184: 185: 186: 187: 188: 189: 190:
191: public static function webalize($s, $charlist = NULL, $lower = TRUE)
192: {
193: $s = self::toAscii($s);
194: if ($lower) {
195: $s = strtolower($s);
196: }
197: $s = preg_replace('#[^a-z0-9' . preg_quote($charlist, '#') . ']+#i', '-', $s);
198: $s = trim($s, '-');
199: return $s;
200: }
201:
202:
203:
204: 205: 206: 207: 208: 209: 210:
211: public static function truncate($s, $maxLen, $append = "\xE2\x80\xA6")
212: {
213: if (self::length($s) > $maxLen) {
214: $maxLen = $maxLen - self::length($append);
215: if ($maxLen < 1) {
216: return $append;
217:
218: } elseif ($matches = self::match($s, '#^.{1,'.$maxLen.'}(?=[\s\x00-/:-@\[-`{-~])#us')) {
219: return $matches[0] . $append;
220:
221: } else {
222: return self::substring($s, 0, $maxLen) . $append;
223: }
224: }
225: return $s;
226: }
227:
228:
229:
230: 231: 232: 233: 234: 235: 236:
237: public static function indent($s, $level = 1, $chars = "\t")
238: {
239: return $level < 1 ? $s : self::replace($s, '#(?:^|[\r\n]+)(?=[^\r\n])#', '$0' . str_repeat($chars, $level));
240: }
241:
242:
243:
244: 245: 246: 247: 248:
249: public static function lower($s)
250: {
251: return mb_strtolower($s, 'UTF-8');
252: }
253:
254:
255:
256: 257: 258: 259: 260:
261: public static function upper($s)
262: {
263: return mb_strtoupper($s, 'UTF-8');
264: }
265:
266:
267:
268: 269: 270: 271: 272:
273: public static function firstUpper($s)
274: {
275: return self::upper(self::substring($s, 0, 1)) . self::substring($s, 1);
276: }
277:
278:
279:
280: 281: 282: 283: 284:
285: public static function capitalize($s)
286: {
287: return mb_convert_case($s, MB_CASE_TITLE, 'UTF-8');
288: }
289:
290:
291:
292: 293: 294: 295: 296: 297: 298:
299: public static function compare($left, $right, $len = NULL)
300: {
301: if ($len < 0) {
302: $left = self::substring($left, $len, -$len);
303: $right = self::substring($right, $len, -$len);
304: } elseif ($len !== NULL) {
305: $left = self::substring($left, 0, $len);
306: $right = self::substring($right, 0, $len);
307: }
308: return self::lower($left) === self::lower($right);
309: }
310:
311:
312:
313: 314: 315: 316: 317:
318: public static function length($s)
319: {
320: return strlen(utf8_decode($s));
321: }
322:
323:
324:
325: 326: 327: 328: 329: 330:
331: public static function trim($s, $charlist = " \t\n\r\0\x0B\xC2\xA0")
332: {
333: $charlist = preg_quote($charlist, '#');
334: return self::replace($s, '#^['.$charlist.']+|['.$charlist.']+$#u', '');
335: }
336:
337:
338:
339: 340: 341: 342: 343: 344: 345:
346: public static function padLeft($s, $length, $pad = ' ')
347: {
348: $length = max(0, $length - self::length($s));
349: $padLen = self::length($pad);
350: return str_repeat($pad, $length / $padLen) . self::substring($pad, 0, $length % $padLen) . $s;
351: }
352:
353:
354:
355: 356: 357: 358: 359: 360: 361:
362: public static function padRight($s, $length, $pad = ' ')
363: {
364: $length = max(0, $length - self::length($s));
365: $padLen = self::length($pad);
366: return $s . str_repeat($pad, $length / $padLen) . self::substring($pad, 0, $length % $padLen);
367: }
368:
369:
370:
371: 372: 373: 374: 375:
376: public static function reverse($s)
377: {
378: return @iconv('UTF-32LE', 'UTF-8', strrev(@iconv('UTF-8', 'UTF-32BE', $s)));
379: }
380:
381:
382:
383: 384: 385: 386: 387: 388:
389: public static function random($length = 10, $charlist = '0-9a-z')
390: {
391: $charlist = str_shuffle(preg_replace_callback('#.-.#', function($m) {
392: return implode('', range($m[0][0], $m[0][2]));
393: }, $charlist));
394: $chLen = strlen($charlist);
395:
396: $s = '';
397: for ($i = 0; $i < $length; $i++) {
398: if ($i % 5 === 0) {
399: $rand = lcg_value();
400: $rand2 = microtime(TRUE);
401: }
402: $rand *= $chLen;
403: $s .= $charlist[($rand + $rand2) % $chLen];
404: $rand -= (int) $rand;
405: }
406: return $s;
407: }
408:
409:
410:
411: 412: 413: 414: 415: 416: 417:
418: public static function split($subject, $pattern, $flags = 0)
419: {
420: Debugger::tryError();
421: $res = preg_split($pattern, $subject, -1, $flags | PREG_SPLIT_DELIM_CAPTURE);
422: if (Debugger::catchError($e) || preg_last_error()) {
423: throw new RegexpException($e ? $e->getMessage() : NULL, $e ? NULL : preg_last_error(), $pattern);
424: }
425: return $res;
426: }
427:
428:
429:
430: 431: 432: 433: 434: 435: 436: 437:
438: public static function match($subject, $pattern, $flags = 0, $offset = 0)
439: {
440: if ($offset > strlen($subject)) {
441: return NULL;
442: }
443: Debugger::tryError();
444: $res = preg_match($pattern, $subject, $m, $flags, $offset);
445: if (Debugger::catchError($e) || preg_last_error()) {
446: throw new RegexpException($e ? $e->getMessage() : NULL, $e ? NULL : preg_last_error(), $pattern);
447: }
448: if ($res) {
449: return $m;
450: }
451: }
452:
453:
454:
455: 456: 457: 458: 459: 460: 461: 462:
463: public static function matchAll($subject, $pattern, $flags = 0, $offset = 0)
464: {
465: if ($offset > strlen($subject)) {
466: return array();
467: }
468: Debugger::tryError();
469: $res = preg_match_all(
470: $pattern, $subject, $m,
471: ($flags & PREG_PATTERN_ORDER) ? $flags : ($flags | PREG_SET_ORDER),
472: $offset
473: );
474: if (Debugger::catchError($e) || preg_last_error()) {
475: throw new RegexpException($e ? $e->getMessage() : NULL, $e ? NULL : preg_last_error(), $pattern);
476: }
477: return $m;
478: }
479:
480:
481:
482: 483: 484: 485: 486: 487: 488: 489:
490: public static function replace($subject, $pattern, $replacement = NULL, $limit = -1)
491: {
492: if (is_object($replacement) || is_array($replacement)) {
493: if ($replacement instanceof Nette\Callback) {
494: $replacement = $replacement->getNative();
495: }
496: if (!is_callable($replacement, FALSE, $textual)) {
497: throw new Nette\InvalidStateException("Callback '$textual' is not callable.");
498: }
499:
500: foreach ((array) $pattern as $tmp) {
501: Debugger::tryError();
502: preg_match($tmp, '');
503: if (Debugger::catchError($e)) {
504: throw new RegexpException($e->getMessage(), NULL, $tmp);
505: }
506: }
507:
508: $res = preg_replace_callback($pattern, $replacement, $subject, $limit);
509: if ($res === NULL && preg_last_error()) {
510: throw new RegexpException(NULL, preg_last_error(), $pattern);
511: }
512: return $res;
513:
514: } elseif ($replacement === NULL && is_array($pattern)) {
515: $replacement = array_values($pattern);
516: $pattern = array_keys($pattern);
517: }
518:
519: Debugger::tryError();
520: $res = preg_replace($pattern, $replacement, $subject, $limit);
521: if (Debugger::catchError($e) || preg_last_error()) {
522: throw new RegexpException($e ? $e->getMessage() : NULL, $e ? NULL : preg_last_error(), $pattern);
523: }
524: return $res;
525: }
526:
527: }
528:
529:
530:
531: 532: 533:
534: class RegexpException extends \Exception
535: {
536: static public $messages = array(
537: PREG_INTERNAL_ERROR => 'Internal error',
538: PREG_BACKTRACK_LIMIT_ERROR => 'Backtrack limit was exhausted',
539: PREG_RECURSION_LIMIT_ERROR => 'Recursion limit was exhausted',
540: PREG_BAD_UTF8_ERROR => 'Malformed UTF-8 data',
541: 5 => 'Offset didn\'t correspond to the begin of a valid UTF-8 code point',
542: );
543:
544: public function __construct($message, $code = NULL, $pattern = NULL)
545: {
546: if (!$message) {
547: $message = (isset(self::$messages[$code]) ? self::$messages[$code] : 'Unknown error') . ($pattern ? " (pattern: $pattern)" : '');
548: } elseif ($pattern) {
549: $message .= " in pattern: $pattern";
550: }
551: parent::__construct($message, $code);
552: }
553:
554: }
555: