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