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