Proyectos de Subversion Moodle

Rev

| Ultima modificación | Ver Log |

Rev Autor Línea Nro. Línea
1441 ariadna 1
<?php
2
 
3
namespace PhpOffice\PhpSpreadsheet\Calculation\TextData;
4
 
5
use PhpOffice\PhpSpreadsheet\Calculation\ArrayEnabled;
6
use PhpOffice\PhpSpreadsheet\Calculation\Exception as CalcExp;
7
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
8
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
9
use PhpOffice\PhpSpreadsheet\Shared\StringHelper;
10
 
11
class Extract
12
{
13
    use ArrayEnabled;
14
 
15
    /**
16
     * LEFT.
17
     *
18
     * @param mixed $value String value from which to extract characters
19
     *                         Or can be an array of values
20
     * @param mixed $chars The number of characters to extract (as an integer)
21
     *                         Or can be an array of values
22
     *
23
     * @return array|string The joined string
24
     *         If an array of values is passed for the $value or $chars arguments, then the returned result
25
     *            will also be an array with matching dimensions
26
     */
27
    public static function left(mixed $value, mixed $chars = 1): array|string
28
    {
29
        if (is_array($value) || is_array($chars)) {
30
            return self::evaluateArrayArguments([self::class, __FUNCTION__], $value, $chars);
31
        }
32
 
33
        try {
34
            $value = Helpers::extractString($value, true);
35
            $chars = Helpers::extractInt($chars, 0, 1);
36
        } catch (CalcExp $e) {
37
            return $e->getMessage();
38
        }
39
 
40
        return mb_substr($value, 0, $chars, 'UTF-8');
41
    }
42
 
43
    /**
44
     * MID.
45
     *
46
     * @param mixed $value String value from which to extract characters
47
     *                         Or can be an array of values
48
     * @param mixed $start Integer offset of the first character that we want to extract
49
     *                         Or can be an array of values
50
     * @param mixed $chars The number of characters to extract (as an integer)
51
     *                         Or can be an array of values
52
     *
53
     * @return array|string The joined string
54
     *         If an array of values is passed for the $value, $start or $chars arguments, then the returned result
55
     *            will also be an array with matching dimensions
56
     */
57
    public static function mid(mixed $value, mixed $start, mixed $chars): array|string
58
    {
59
        if (is_array($value) || is_array($start) || is_array($chars)) {
60
            return self::evaluateArrayArguments([self::class, __FUNCTION__], $value, $start, $chars);
61
        }
62
 
63
        try {
64
            $value = Helpers::extractString($value, true);
65
            $start = Helpers::extractInt($start, 1);
66
            $chars = Helpers::extractInt($chars, 0);
67
        } catch (CalcExp $e) {
68
            return $e->getMessage();
69
        }
70
 
71
        return mb_substr($value, --$start, $chars, 'UTF-8');
72
    }
73
 
74
    /**
75
     * RIGHT.
76
     *
77
     * @param mixed $value String value from which to extract characters
78
     *                         Or can be an array of values
79
     * @param mixed $chars The number of characters to extract (as an integer)
80
     *                         Or can be an array of values
81
     *
82
     * @return array|string The joined string
83
     *         If an array of values is passed for the $value or $chars arguments, then the returned result
84
     *            will also be an array with matching dimensions
85
     */
86
    public static function right(mixed $value, mixed $chars = 1): array|string
87
    {
88
        if (is_array($value) || is_array($chars)) {
89
            return self::evaluateArrayArguments([self::class, __FUNCTION__], $value, $chars);
90
        }
91
 
92
        try {
93
            $value = Helpers::extractString($value, true);
94
            $chars = Helpers::extractInt($chars, 0, 1);
95
        } catch (CalcExp $e) {
96
            return $e->getMessage();
97
        }
98
 
99
        return mb_substr($value, mb_strlen($value, 'UTF-8') - $chars, $chars, 'UTF-8');
100
    }
101
 
102
    /**
103
     * TEXTBEFORE.
104
     *
105
     * @param mixed $text the text that you're searching
106
     *                    Or can be an array of values
107
     * @param null|array|string $delimiter the text that marks the point before which you want to extract
108
     *                                 Multiple delimiters can be passed as an array of string values
109
     * @param mixed $instance The instance of the delimiter after which you want to extract the text.
110
     *                            By default, this is the first instance (1).
111
     *                            A negative value means start searching from the end of the text string.
112
     *                        Or can be an array of values
113
     * @param mixed $matchMode Determines whether the match is case-sensitive or not.
114
     *                           0 - Case-sensitive
115
     *                           1 - Case-insensitive
116
     *                        Or can be an array of values
117
     * @param mixed $matchEnd Treats the end of text as a delimiter.
118
     *                          0 - Don't match the delimiter against the end of the text.
119
     *                          1 - Match the delimiter against the end of the text.
120
     *                        Or can be an array of values
121
     * @param mixed $ifNotFound value to return if no match is found
122
     *                             The default is a #N/A Error
123
     *                          Or can be an array of values
124
     *
125
     * @return array|string the string extracted from text before the delimiter; or the $ifNotFound value
126
     *         If an array of values is passed for any of the arguments, then the returned result
127
     *            will also be an array with matching dimensions
128
     */
129
    public static function before(mixed $text, $delimiter, mixed $instance = 1, mixed $matchMode = 0, mixed $matchEnd = 0, mixed $ifNotFound = '#N/A'): array|string
130
    {
131
        if (is_array($text) || is_array($instance) || is_array($matchMode) || is_array($matchEnd) || is_array($ifNotFound)) {
132
            return self::evaluateArrayArgumentsIgnore([self::class, __FUNCTION__], 1, $text, $delimiter, $instance, $matchMode, $matchEnd, $ifNotFound);
133
        }
134
 
135
        try {
136
            $text = Helpers::extractString($text ?? '', true);
137
            Helpers::extractString(Functions::flattenSingleValue($delimiter ?? ''), true);
138
        } catch (CalcExp $e) {
139
            return $e->getMessage();
140
        }
141
 
142
        $instance = (int) $instance;
143
        $matchMode = (int) $matchMode;
144
        $matchEnd = (int) $matchEnd;
145
 
146
        $split = self::validateTextBeforeAfter($text, $delimiter, $instance, $matchMode, $matchEnd, $ifNotFound);
147
        if (is_string($split)) {
148
            return $split;
149
        }
150
        if (Helpers::extractString(Functions::flattenSingleValue($delimiter ?? '')) === '') {
151
            return ($instance > 0) ? '' : $text;
152
        }
153
 
154
        // Adjustment for a match as the first element of the split
155
        $flags = self::matchFlags($matchMode);
156
        $delimiter = self::buildDelimiter($delimiter);
157
        $adjust = preg_match('/^' . $delimiter . "\$/{$flags}", $split[0]);
158
        $oddReverseAdjustment = count($split) % 2;
159
 
160
        $split = ($instance < 0)
161
            ? array_slice($split, 0, max(count($split) - (abs($instance) * 2 - 1) - $adjust - $oddReverseAdjustment, 0))
162
            : array_slice($split, 0, $instance * 2 - 1 - $adjust);
163
 
164
        return implode('', $split);
165
    }
166
 
167
    /**
168
     * TEXTAFTER.
169
     *
170
     * @param mixed $text the text that you're searching
171
     * @param null|array|string $delimiter the text that marks the point before which you want to extract
172
     *                                 Multiple delimiters can be passed as an array of string values
173
     * @param mixed $instance The instance of the delimiter after which you want to extract the text.
174
     *                          By default, this is the first instance (1).
175
     *                          A negative value means start searching from the end of the text string.
176
     *                        Or can be an array of values
177
     * @param mixed $matchMode Determines whether the match is case-sensitive or not.
178
     *                            0 - Case-sensitive
179
     *                            1 - Case-insensitive
180
     *                         Or can be an array of values
181
     * @param mixed $matchEnd Treats the end of text as a delimiter.
182
     *                          0 - Don't match the delimiter against the end of the text.
183
     *                          1 - Match the delimiter against the end of the text.
184
     *                        Or can be an array of values
185
     * @param mixed $ifNotFound value to return if no match is found
186
     *                             The default is a #N/A Error
187
     *                          Or can be an array of values
188
     *
189
     * @return array|string the string extracted from text before the delimiter; or the $ifNotFound value
190
     *         If an array of values is passed for any of the arguments, then the returned result
191
     *            will also be an array with matching dimensions
192
     */
193
    public static function after(mixed $text, $delimiter, mixed $instance = 1, mixed $matchMode = 0, mixed $matchEnd = 0, mixed $ifNotFound = '#N/A'): array|string
194
    {
195
        if (is_array($text) || is_array($instance) || is_array($matchMode) || is_array($matchEnd) || is_array($ifNotFound)) {
196
            return self::evaluateArrayArgumentsIgnore([self::class, __FUNCTION__], 1, $text, $delimiter, $instance, $matchMode, $matchEnd, $ifNotFound);
197
        }
198
 
199
        try {
200
            $text = Helpers::extractString($text ?? '', true);
201
            Helpers::extractString(Functions::flattenSingleValue($delimiter ?? ''), true);
202
        } catch (CalcExp $e) {
203
            return $e->getMessage();
204
        }
205
 
206
        $instance = (int) $instance;
207
        $matchMode = (int) $matchMode;
208
        $matchEnd = (int) $matchEnd;
209
 
210
        $split = self::validateTextBeforeAfter($text, $delimiter, $instance, $matchMode, $matchEnd, $ifNotFound);
211
        if (is_string($split)) {
212
            return $split;
213
        }
214
        if (Helpers::extractString(Functions::flattenSingleValue($delimiter ?? '')) === '') {
215
            return ($instance < 0) ? '' : $text;
216
        }
217
 
218
        // Adjustment for a match as the first element of the split
219
        $flags = self::matchFlags($matchMode);
220
        $delimiter = self::buildDelimiter($delimiter);
221
        $adjust = preg_match('/^' . $delimiter . "\$/{$flags}", $split[0]);
222
        $oddReverseAdjustment = count($split) % 2;
223
 
224
        $split = ($instance < 0)
225
            ? array_slice($split, count($split) - ((int) abs($instance + 1) * 2) - $adjust - $oddReverseAdjustment)
226
            : array_slice($split, $instance * 2 - $adjust);
227
 
228
        return implode('', $split);
229
    }
230
 
231
    private static function validateTextBeforeAfter(string $text, null|array|string $delimiter, int $instance, int $matchMode, int $matchEnd, mixed $ifNotFound): array|string
232
    {
233
        $flags = self::matchFlags($matchMode);
234
        $delimiter = self::buildDelimiter($delimiter);
235
 
236
        if (preg_match('/' . $delimiter . "/{$flags}", $text) === 0 && $matchEnd === 0) {
237
            return $ifNotFound;
238
        }
239
 
240
        $split = preg_split('/' . $delimiter . "/{$flags}", $text, 0, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE);
241
        if ($split === false) {
242
            return ExcelError::NA();
243
        }
244
 
245
        if ($instance === 0 || abs($instance) > StringHelper::countCharacters($text)) {
246
            return ExcelError::VALUE();
247
        }
248
 
249
        if ($matchEnd === 0 && (abs($instance) > floor(count($split) / 2))) {
250
            return ExcelError::NA();
251
        } elseif ($matchEnd !== 0 && (abs($instance) - 1 > ceil(count($split) / 2))) {
252
            return ExcelError::NA();
253
        }
254
 
255
        return $split;
256
    }
257
 
258
    /**
259
     * @param null|array|string $delimiter the text that marks the point before which you want to extract
260
     *                                 Multiple delimiters can be passed as an array of string values
261
     */
262
    private static function buildDelimiter($delimiter): string
263
    {
264
        if (is_array($delimiter)) {
265
            $delimiter = Functions::flattenArray($delimiter);
266
            $quotedDelimiters = array_map(
267
                fn ($delimiter): string => preg_quote($delimiter ?? '', '/'),
268
                $delimiter
269
            );
270
            $delimiters = implode('|', $quotedDelimiters);
271
 
272
            return '(' . $delimiters . ')';
273
        }
274
 
275
        return '(' . preg_quote($delimiter ?? '', '/') . ')';
276
    }
277
 
278
    private static function matchFlags(int $matchMode): string
279
    {
280
        return ($matchMode === 0) ? 'mu' : 'miu';
281
    }
282
}