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