]> git.mxchange.org Git - core.git/blob - framework/main/classes/utils/strings/class_StringUtils.php
Continued:
[core.git] / framework / main / classes / utils / strings / class_StringUtils.php
1 <?php
2 // Own namespace
3 namespace Org\Mxchange\CoreFramework\Utils\Strings;
4
5 // Import framework stuff
6 use Org\Mxchange\CoreFramework\Bootstrap\FrameworkBootstrap;
7 use Org\Mxchange\CoreFramework\Configuration\FrameworkConfiguration;
8 use Org\Mxchange\CoreFramework\Generic\NullPointerException;
9 use Org\Mxchange\CoreFramework\Object\BaseFrameworkSystem;
10
11 // Import SPL stuff
12 use \InvalidArgumentException;
13
14 /**
15  * A string utility class
16  *
17  * @author              Roland Haeder <webmaster@ship-simu.org>
18  * @version             0.0.0
19  * @copyright   Copyright (c) 2007, 2008 Roland Haeder, 2009 - 2021 Core Developer Team
20  * @license             GNU GPL 3.0 or any newer version
21  * @link                http://www.ship-simu.org
22  *
23  * This program is free software: you can redistribute it and/or modify
24  * it under the terms of the GNU General Public License as published by
25  * the Free Software Foundation, either version 3 of the License, or
26  * (at your option) any later version.
27  *
28  * This program is distributed in the hope that it will be useful,
29  * but WITHOUT ANY WARRANTY; without even the implied warranty of
30  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
31  * GNU General Public License for more details.
32  *
33  * You should have received a copy of the GNU General Public License
34  * along with this program. If not, see <http://www.gnu.org/licenses/>.
35  */
36 final class StringUtils extends BaseFrameworkSystem {
37         /**
38          * Thousands separator
39          */
40         private static $thousands = ''; // German
41
42         /**
43          * Decimal separator
44          */
45         private static $decimals  = ''; // German
46
47         /**
48          * In-method cache array
49          */
50         private static $cache = [];
51
52         /**
53          * Array with bitmasks and such for pack/unpack methods to support both
54          * 32-bit and 64-bit systems
55          */
56         private static $packingData = [
57                 32 => [
58                         'step'   => 3,
59                         'left'   => 0xffff0000,
60                         'right'  => 0x0000ffff,
61                         'factor' => 16,
62                         'format' => 'II',
63                 ],
64                 64 => [
65                         'step'   => 7,
66                         'left'   => 0xffffffff00000000,
67                         'right'  => 0x00000000ffffffff,
68                         'factor' => 32,
69                         'format' => 'NN'
70                 ]
71         ];
72
73         /**
74          * Hexadecimal->Decimal translation array
75          */
76         private static $hexdec = [
77                 '0' => 0,
78                 '1' => 1,
79                 '2' => 2,
80                 '3' => 3,
81                 '4' => 4,
82                 '5' => 5,
83                 '6' => 6,
84                 '7' => 7,
85                 '8' => 8,
86                 '9' => 9,
87                 'a' => 10,
88                 'b' => 11,
89                 'c' => 12,
90                 'd' => 13,
91                 'e' => 14,
92                 'f' => 15
93         ];
94
95         /**
96          * Decimal->hexadecimal translation array
97          */
98         private static $dechex = [
99                  0 => '0',
100                  1 => '1',
101                  2 => '2',
102                  3 => '3',
103                  4 => '4',
104                  5 => '5',
105                  6 => '6',
106                  7 => '7',
107                  8 => '8',
108                  9 => '9',
109                 10 => 'a',
110                 11 => 'b',
111                 12 => 'c',
112                 13 => 'd',
113                 14 => 'e',
114                 15 => 'f'
115         ];
116
117         /**
118          * Simple 64-bit check, thanks to "Salman A" from stackoverflow.com:
119          *
120          * The integer size is 4 bytes on 32-bit and 8 bytes on a 64-bit system.
121          */
122         private static $archArrayElement = 0;
123
124         /**
125          * Private constructor, no instance needed. If PHP would have a static initializer ...
126          *
127          * @return      void
128          */
129         private function __construct () {
130                 // Call parent constructor
131                 parent::__construct(__CLASS__);
132
133                 // Is one not set?
134                 if (empty(self::$archArrayElement)) {
135                         // Set array element
136                         self::$archArrayElement = (PHP_INT_SIZE === 8 ? 64 : 32);
137
138                         // Init from configuration
139                         self::$thousands = FrameworkBootstrap::getConfigurationInstance()->getConfigEntry('thousands_separator');
140                         self::$decimals = FrameworkBootstrap::getConfigurationInstance()->getConfigEntry('decimals_separator');
141                 }
142         }
143
144         /**
145          * Converts dashes to underscores, e.g. useable for configuration entries
146          *
147          * @param       $str    The string with maybe dashes inside
148          * @return      $str    The converted string with no dashed, but underscores
149          * @throws      NullPointerException    If $str is null
150          * @throws      InvalidArgumentException        If $str is empty
151          */
152         public static function convertDashesToUnderscores (string $str) {
153                 // Validate parameter
154                 if (empty($str)) {
155                         // Entry is empty
156                         throw new InvalidArgumentException('Parameter "str" is empty');
157                 }
158
159                 // Convert them all
160                 $str = str_replace('-', '_', $str);
161
162                 // Return converted string
163                 return $str;
164         }
165
166         /**
167          * Encodes raw data (almost any type) by "serializing" it and then pack it
168          * into a "binary format".
169          *
170          * @param       $rawData        Raw data (almost any type)
171          * @return      $encoded        Encoded data
172          * @throws      InvalidArgumentException        If $rawData has a non-serializable data type
173          */
174         public static function encodeData ($rawData) {
175                 // Make sure no objects or resources pass through
176                 //* NOISY-DEBUG */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('STRING-UTILS: rawData[]=%s - CALLED!', gettype($rawData)));
177                 if (is_object($rawData) || is_resource($rawData)) {
178                         // Not all variable types should be serialized here
179                         throw new InvalidArgumentException(sprintf('rawData[]=%s cannot be serialized.', gettype($rawData)));
180                 }
181
182                 // Init instance
183                 $dummyInstance = new StringUtils();
184
185                 // First "serialize" it (json_encode() is faster than serialize())
186                 $encoded = self::packString(json_encode($rawData));
187
188                 // And return it
189                 //* NOISY-DEBUG */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('STRING-UTILS: encoded()=%d - EXIT!', strlen($encoded)));
190                 return $encoded;
191         }
192
193         /**
194          * Converts e.g. a command from URL to a valid class by keeping out bad characters
195          *
196          * @param       $str            The string, what ever it is needs to be converted
197          * @return      $className      Generated class name
198          * @throws      InvalidArgumentException        If a paramter is invalid
199          */
200         public static final function convertToClassName (string $str) {
201                 // Is the parameter valid?
202                 //* NOISY-DEBUG */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('STRING-UTILS: str=%s - CALLED!', $str));
203                 if (empty($str)) {
204                         // No empty strings, please
205                         throw new InvalidArgumentException('Parameter "str" is empty', self::EXCEPTION_CONFIG_KEY_IS_EMPTY);
206                 } elseif (!isset(self::$cache[$str])) {
207                         // Init class name
208                         $className = '';
209
210                         // Convert all dashes in underscores
211                         $str = self::convertDashesToUnderscores($str);
212
213                         // Now use that underscores to get classname parts for hungarian style
214                         //* NOISY-DEBUG */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('STRING-UTILS: str=%s - AFTER!', $str));
215                         foreach (explode('_', $str) as $strPart) {
216                                 // Make the class name part lower case and first upper case
217                                 $className .= ucfirst(strtolower($strPart));
218                         }
219
220                         // Set cache
221                         //* NOISY-DEBUG */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('STRING-UTILS: self[%s]=%s - SET!', $str, $className));
222                         self::$cache[$str] = $className;
223                 }
224
225                 // Return class name
226                 //* NOISY-DEBUG */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('STRING-UTILS: self[%s]=%s - EXIT!', $str, $className));
227                 return self::$cache[$str];
228         }
229
230         /**
231          * Formats computer generated price values into human-understandable formats
232          * with thousand and decimal separators.
233          *
234          * @param       $value          The in computer format value for a price
235          * @param       $currency       The currency symbol (use HTML-valid characters!)
236          * @param       $decNum         Number of decimals after commata
237          * @return      $price          The for the current language formated price string
238          * @throws      MissingDecimalsThousandsSeparatorException      If decimals or thousands separator is missing
239          */
240         public static function formatCurrency (float $value, string $currency = '&euro;', int $decNum = 2) {
241                 // Init instance
242                 $dummyInstance = new StringUtils();
243
244                 // Reformat the US number
245                 $price = number_format($value, $decNum, self::$decimals, self::$thousands) . $currency;
246
247                 // Return as string...
248                 return $price;
249         }
250
251         /**
252          * Converts a hexadecimal string, even with negative sign as first string to
253          * a decimal number using BC functions.
254          *
255          * This work is based on comment #86673 on php.net documentation page at:
256          * <http://de.php.net/manual/en/function.dechex.php#86673>
257          *
258          * @param       $hex    Hexadecimal string
259          * @return      $dec    Decimal number
260          */
261         public static function hex2dec (string $hex) {
262                 // Convert to all lower-case
263                 $hex = strtolower($hex);
264
265                 // Detect sign (negative/positive numbers)
266                 $sign = '';
267                 if (substr($hex, 0, 1) == '-') {
268                         $sign = '-';
269                         $hex = substr($hex, 1);
270                 }
271
272                 // Decode the hexadecimal string into a decimal number
273                 $dec = 0;
274                 for ($i = strlen($hex) - 1, $e = 1; $i >= 0; $i--, $e = bcmul($e, 16)) {
275                         $factor = self::$hexdec[substr($hex, $i, 1)];
276                         $dec = bcadd($dec, bcmul($factor, $e));
277                 }
278
279                 // Return the decimal number
280                 return $sign . $dec;
281         }
282
283         /**
284          * Converts even very large decimal numbers, also signed, to a hexadecimal
285          * string.
286          *
287          * This work is based on comment #97756 on php.net documentation page at:
288          * <http://de.php.net/manual/en/function.hexdec.php#97756>
289          *
290          * @param       $dec            Decimal number, even with negative sign
291          * @param       $maxLength      Optional maximum length of the string
292          * @return      $hex    Hexadecimal string
293          */
294         public static function dec2hex (string $dec, int $maxLength = 0) {
295                 // maxLength can be zero or devideable by 2
296                 assert(($maxLength == 0) || (($maxLength % 2) == 0));
297
298                 // Detect sign (negative/positive numbers)
299                 $sign = '';
300                 if ($dec < 0) {
301                         $sign = '-';
302                         $dec = abs($dec);
303                 }
304
305                 // Encode the decimal number into a hexadecimal string
306                 $hex = '';
307                 do {
308                         $hex = self::$dechex[($dec % (2 ^ 4))] . $hex;
309                         $dec /= (2 ^ 4);
310                 } while ($dec >= 1);
311
312                 /*
313                  * Leading zeros are required for hex-decimal "numbers". In some
314                  * situations more leading zeros are wanted, so check for both
315                  * conditions.
316                  */
317                 if ($maxLength > 0) {
318                         // Prepend more zeros
319                         $hex = str_pad($hex, $maxLength, '0', STR_PAD_LEFT);
320                 } elseif ((strlen($hex) % 2) != 0) {
321                         // Only make string's length dividable by 2
322                         $hex = '0' . $hex;
323                 }
324
325                 // Return the hexadecimal string
326                 return $sign . $hex;
327         }
328
329         /**
330          * Converts a ASCII string (0 to 255) into a decimal number.
331          *
332          * @param       $asc    The ASCII string to be converted
333          * @return      $dec    Decimal number
334          */
335         public static function asc2dec (string $asc) {
336                 // Convert it into a hexadecimal number
337                 $hex = bin2hex($asc);
338
339                 // And back into a decimal number
340                 $dec = self::hex2dec($hex);
341
342                 // Return it
343                 return $dec;
344         }
345
346         /**
347          * Converts a decimal number into an ASCII string.
348          *
349          * @param       $dec            Decimal number
350          * @return      $asc    An ASCII string
351          */
352         public static function dec2asc (string $dec) {
353                 // First convert the number into a hexadecimal string
354                 $hex = self::dec2hex($dec);
355
356                 // Then convert it into the ASCII string
357                 $asc = self::hex2asc($hex);
358
359                 // Return it
360                 return $asc;
361         }
362
363         /**
364          * Converts a hexadecimal number into an ASCII string. Negative numbers
365          * are not allowed.
366          *
367          * @param       $hex    Hexadecimal string
368          * @return      $asc    An ASCII string
369          */
370         public static function hex2asc ($hex) {
371                 // Check for length, it must be devideable by 2
372                 //* DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('hex='.$hex);
373                 assert((strlen($hex) % 2) == 0);
374
375                 // Walk the string
376                 $asc = '';
377                 for ($idx = 0; $idx < strlen($hex); $idx+=2) {
378                         // Get the decimal number of the chunk
379                         $part = hexdec(substr($hex, $idx, 2));
380
381                         // Add it to the final string
382                         $asc .= chr($part);
383                 }
384
385                 // Return the final string
386                 return $asc;
387         }
388
389         /**
390          * Pack a string into a "binary format". Please execuse me that this is
391          * widely undocumented. :-(
392          *
393          * @param       $str            Unpacked string
394          * @return      $packed         Packed string
395          * @todo        Improve documentation
396          */
397         private static function packString (string $str) {
398                 // First compress the string (gzcompress is okay)
399                 //* NOISY-DEBUG */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('STRING-UTILS: str=%s - CALLED!', $str));
400                 $str = gzcompress($str);
401
402                 // Init variable
403                 $packed = '';
404
405                 // And start the "encoding" loop
406                 for ($idx = 0; $idx < strlen($str); $idx += self::$packingData[self::$archArrayElement]['step']) {
407                         $big = 0;
408                         for ($i = 0; $i < self::$packingData[self::$archArrayElement]['step']; $i++) {
409                                 $factor = (self::$packingData[self::$archArrayElement]['step'] - 1 - $i);
410
411                                 if (($idx + $i) <= strlen($str)) {
412                                         $ord = ord(substr($str, ($idx + $i), 1));
413
414                                         $add = $ord * pow(256, $factor);
415
416                                         $big += $add;
417
418                                         //print 'idx=' . $idx . ',i=' . $i . ',ord=' . $ord . ',factor=' . $factor . ',add=' . $add . ',big=' . $big . PHP_EOL;
419                                 }
420                         }
421
422                         // Left/right parts (low/high?)
423                         $l = ($big & self::$packingData[self::$archArrayElement]['left']) >>self::$packingData[self::$archArrayElement]['factor'];
424                         $r = $big & self::$packingData[self::$archArrayElement]['right'];
425
426                         // Create chunk
427                         $chunk = str_pad(pack(self::$packingData[self::$archArrayElement]['format'], $l, $r), 8, '0', STR_PAD_LEFT);
428                         //* NOISY-DEBUG */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('STRING-UTILS: big=%d,chunk(%d)=%s', $big, strlen($chunk), md5($chunk)));
429
430                         $packed .= $chunk;
431                 }
432
433                 // Return it
434                 //* NOISY-DEBUG */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('STRING-UTILS: packed=%s - EXIT!', $packed));
435                 return $packed;
436         }
437
438         /**
439          * Checks whether the given hexadecimal number is really a hex-number (only chars 0-9,a-f).
440          *
441          * @param       $num    A string consisting only chars between 0 and 9
442          * @param       $assertMismatch         Whether to assert mismatches
443          * @return      $ret    The (hopefully) secured hext-numbered value
444          */
445         public static function hexval (string $num, bool $assertMismatch = false) {
446                 // Filter all numbers out
447                 $ret = preg_replace('/[^0123456789abcdefABCDEF]/', '', $num);
448
449                 // Assert only if requested
450                 if ($assertMismatch === true) {
451                         // Has the whole value changed?
452                         assert(('' . $ret . '' != '' . $num . '') && (!is_null($num)));
453                 }
454
455                 // Return result
456                 return $ret;
457         }
458
459 }