1<?php
2
3namespace ComboStrap;
4
5use ComboStrap\Web\Url;
6
7/**
8 * Class StringUtility
9 * @package ComboStrap
10 * A class with string utility
11 */
12class StringUtility
13{
14
15    public const SEPARATORS_CHARACTERS = [".", "(", ")", ",", "-"];
16
17
18    /**
19     * Generate a text with a max length of $length
20     * and add ... if above
21     * @param $myString
22     * @param $length
23     * @return string
24     */
25    static function truncateString($myString, $length): string
26    {
27
28        if (strlen($myString) > $length) {
29            $suffix = ' ...';
30            $myString = substr($myString, 0, ($length - 1) - strlen($suffix)) . $suffix;
31        }
32        return $myString;
33    }
34
35    /**
36     * @param $string
37     * @return string - the string without any carriage return
38     * Used to compare string without worrying about carriage return
39     */
40    public static function normalized($string)
41    {
42        return str_replace("\n", "", $string);
43    }
44
45    /**
46     * @param $needle
47     * @param $haystack
48     * @return bool
49     */
50    public static function contain($needle, $haystack)
51    {
52        $pos = strpos($haystack, $needle);
53        if ($pos === FALSE) {
54            return false;
55        } else {
56            return true;
57        }
58    }
59
60    public static function toString($value)
61    {
62        /**
63         * No transformation if it's a string
64         * var_export below is not idempotent
65         * ie \ would become \\
66         */
67        if (is_string($value)) {
68            return $value;
69        }
70
71        if (is_array($value)) {
72            $string = var_export($value, true);
73
74            // An array value gets command in var_export
75            $lastCharacterIndex = strlen($string) - 1;
76            if ($string[0] === "'" && $string[$lastCharacterIndex] === "'") {
77                $string = substr($string, 1, strlen($string) - 2);
78            }
79            return $string;
80        }
81
82        if (is_object($value)) {
83            if (method_exists($value, "__toString")) {
84                return strval($value);
85            } else {
86                return get_class($value);
87            }
88        }
89
90        if (is_numeric($value)) {
91            return strval($value);
92        }
93
94        if (is_bool($value)) {
95            return var_export($value, true);
96        }
97
98        $string = var_export($value, true);
99        LogUtility::msg("The type of the value ($string) is unknown and could not be properly cast to string", LogUtility::LVL_MSG_WARNING);
100        return $string;
101
102    }
103
104    /**
105     * Add an EOL if not present at the end of the string
106     * @param $doc
107     */
108    public static function addEolCharacterIfNotPresent(&$doc)
109    {
110        $strlen = strlen($doc);
111        if ($strlen < 1) {
112            return;
113        }
114        if ($doc[$strlen - 1] != DOKU_LF) {
115            $doc .= DOKU_LF;
116        }
117    }
118
119    /**
120     * Delete the string from the end
121     * This is used generally to delete the previous opening tag of an header or a blockquote
122     * @param $doc
123     * @param $string
124     */
125    public static function rtrim(&$doc, $string)
126    {
127
128        /**
129         * We trim because in the process, we may get extra {@link DOKU_LF} at the end
130         */
131        $doc = trim($doc);
132        $string = trim($string);
133        $length = strlen($doc) - strlen($string);
134        if (substr($doc, $length) === $string) {
135            $doc = substr($doc, 0, $length);
136        }
137
138    }
139
140    /**
141     * Delete the string from the beginning
142     * This is used to delete a tag for instance
143     * @param $doc
144     * @param $string
145     */
146    public static function ltrim(&$doc, $string)
147    {
148
149        $doc = trim($doc);
150        $string = trim($string);
151        $length = strlen($string);
152        if (substr($doc, 0, $length) === $string) {
153            $doc = substr($doc, $length);
154        }
155
156    }
157
158    /**
159     * The word count does not take into account
160     * words with non-words characters such as < =
161     * Therefore the node <node> and attribute name=value are not taken in the count
162     * @param $text
163     * @return int the number of words
164     */
165    public static function getWordCount($text)
166    {
167        /**
168         * Delete the frontmatter
169         */
170        $text = preg_replace("/^---(json)?$.*^---$/Ums", "", $text);
171        /**
172         * New line for node
173         */
174        $text = str_replace("<", "\n<", $text);
175        $text = str_replace(">", ">\n", $text);
176        // \s shorthand for whitespace
177        // | the table and links are separated with a |
178        // / to take into account expression such as and/or
179        // /u for unicode support (https://www.php.net/manual/en/reference.pcre.pattern.modifiers.php)
180        $wordSeparator = '/[\s|\/]/u';
181        $preg_split = preg_split($wordSeparator, $text);
182        $wordsWithoutEmpty = array_filter($preg_split, self::class . '::isWord');
183        return count($wordsWithoutEmpty);
184    }
185
186    public static function normalize($expected)
187    {
188        $expected = preg_replace("/[\s]/", " ", $expected);
189        $expected = str_replace("  ", " ", $expected);
190        $expected = str_replace("  ", " ", $expected);
191        $expected = str_replace("  ", " ", $expected);
192        $expected = str_replace("  ", " ", $expected);
193        return trim($expected);
194
195    }
196
197    /**
198     * @param $text
199     * @return bool
200     */
201    public static function isWord($text)
202    {
203        if (empty($text)) {
204            return false;
205        }
206        /**
207         * We also allow `-` minus
208         *
209         * And because otherwise the words are not counted:
210         *   * `'` (used to highlight words)
211         *   * `[]` used in links
212         *   * `,` used at the end of a sentenct
213         */
214        $preg_match = preg_match("/^[\w\-'\]\[,]*$/u", $text);
215        return $preg_match == 1;
216    }
217
218    public static function match($subject, $pattern)
219    {
220        return preg_match("/$pattern/", $subject) === 1;
221    }
222
223    public static function endWiths($string, $suffix)
224    {
225        $suffixStartPosition = strlen($string) - strlen($suffix);
226        return strrpos($string, $suffix) === $suffixStartPosition;
227    }
228
229    public static function explodeAndTrim($string, $delimiter = ",")
230    {
231        return array_map('trim', explode($delimiter, $string));
232    }
233
234    public static function lastIndexOf($haystack, $needle)
235    {
236        /**
237         * strRpos
238         * and not strpos
239         */
240        return strrpos($haystack, $needle);
241    }
242
243    public static function startWiths($string, $prefix)
244    {
245        return strrpos($string, $prefix) === 0;
246    }
247
248    /**
249     * @param $string
250     * @param null $separatorsCharacters - characters that will separate the words
251     * @return array a words
252     */
253    public static function getWords($string, $separatorsCharacters = null): array
254    {
255        // Reserved characters to space
256        if ($separatorsCharacters === null) {
257            $separatorsCharacters = StringUtility::getAllSeparators();
258        }
259        if (!is_array($separatorsCharacters)) {
260            LogUtility::msg("The separators characters are not an array, default characters used");
261            $separatorsCharacters = StringUtility::getAllSeparators();
262        }
263
264        $string = str_replace($separatorsCharacters, " ", $string);
265        // Doubles spaces to space
266        $string = preg_replace("/\s{2,}/", " ", $string);
267        // Trim space
268        $string = trim($string);
269
270        return explode(" ", $string);
271    }
272
273    private static function getAllSeparators(): array
274    {
275        return array_merge(
276            Url::RESERVED_WORDS,
277            LocalPath::RESERVED_WINDOWS_CHARACTERS,
278            StringUtility::SEPARATORS_CHARACTERS
279        );
280    }
281
282}
283