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