Completed
Pull Request — master (#178)
by ignace nyamagana
12:35
created

Query::stripBom()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 12
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
c 2
b 0
f 0
dl 0
loc 12
rs 9.4285
cc 2
eloc 7
nc 2
nop 1
1
<?php
2
/**
3
* This file is part of the League.csv library
4
*
5
* @license http://opensource.org/licenses/MIT
6
* @link https://github.com/thephpleague/csv/
7
* @version 8.1.1
8
* @package League.csv
9
*
10
* For the full copyright and license information, please view the LICENSE
11
* file that was distributed with this source code.
12
*/
13
namespace League\Csv;
14
15
use ArrayIterator;
16
use CallbackFilterIterator;
17
use Iterator;
18
use League\Csv\Config\Validator;
19
use LimitIterator;
20
21
/**
22
 * A simple Query class to fetch rows against a Csv file object
23
 *
24
 * @package League.csv
25
 * @since  4.2.1
26
 *
27
 */
28
class Query
29
{
30
    use Validator;
31
32
    /**
33
     * Callables to filter the iterator
34
     *
35
     * @var callable[]
36
     */
37
    protected $filters = [];
38
39
    /**
40
     * Callables to sort the iterator
41
     *
42
     * @var callable[]
43
     */
44
    protected $sort_by = [];
45
46
    /**
47
     * iterator Offset
48
     *
49
     * @var int
50
     */
51
    protected $offset = 0;
52
53
    /**
54
     * iterator maximum length
55
     *
56
     * @var int
57
     */
58
    protected $limit = -1;
59
60
    /**
61
     * Stripping BOM status
62
     *
63
     * @var bool
64
     */
65
    protected $strip_bom = false;
66
67
    /**
68
     * Returns the CSV Iterator
69
     *
70
     * @return Iterator
71
     */
72
    public function __invoke(AbstractCsv $csv)
73
    {
74
        $input_encoding = $csv->getInputEncoding();
75
        $normalizedCsv = function ($row) {
76
            return is_array($row) && $row != [null];
77
        };
78
        array_unshift($this->filters, $normalizedCsv);
79
        $iterator = $this->applyBomStripping($csv);
80
        $iterator = $this->applyIteratorFilter($iterator);
81
        $iterator = $this->applyIteratorSortBy($iterator);
82
        $iterator = new LimitIterator($iterator, $this->offset, $this->limit);
83
84
        return $this->convertToUtf8($input_encoding, $iterator);
85
    }
86
87
    /**
88
     * Stripping BOM setter
89
     *
90
     * @param bool $status
91
     *
92
     * @return static
93
     */
94
    public function stripBom($status)
95
    {
96
        $status = (bool) $status;
97
        if ($status === $this->strip_bom) {
98
            return $this;
99
        }
100
101
        $clone = clone $this;
102
        $clone->strip_bom = (bool) $status;
103
104
        return $clone;
105
    }
106
107
    /**
108
     * Set LimitIterator Offset
109
     *
110
     * @param $offset
111
     *
112
     * @return $this
113
     */
114
    public function setOffset($offset)
115
    {
116
        $offset = $this->validateInteger($offset, 0, 'the offset must be a positive integer or 0');
117
        if ($offset === $this->offset) {
118
            return $this;
119
        }
120
121
        $clone = clone $this;
122
        $clone->offset = $offset;
123
124
        return $clone;
125
    }
126
127
    /**
128
     * Set LimitIterator Count
129
     *
130
     * @param int $limit
131
     *
132
     * @return $this
133
     */
134
    public function setLimit($limit)
135
    {
136
        $limit = $this->validateInteger($limit, -1, 'the limit must an integer greater or equals to -1');
137
        if ($limit === $this->limit) {
138
            return $this;
139
        }
140
141
        $clone = clone $this;
142
        $clone->limit = $limit;
143
144
        return $clone;
145
    }
146
147
    /**
148
     * Set an Iterator sorting callable function
149
     *
150
     * @param callable $callable
151
     *
152
     * @return $this
153
     */
154
    public function addSortBy(callable $callable)
155
    {
156
        $clone = clone $this;
157
        $clone->sort_by[] = $callable;
158
159
        return $clone;
160
    }
161
162
    /**
163
     * Set the Iterator filter method
164
     *
165
     * @param callable $callable
166
     *
167
     * @return $this
168
     */
169
    public function addFilter(callable $callable)
170
    {
171
        $clone = clone $this;
172
        $clone->filters[] = $callable;
173
174
        return $clone;
175
    }
176
177
    /**
178
     * Remove the BOM sequence from the CSV
179
     *
180
     * @param AbstractCsv $csv
181
     *
182
     * @return Iterator
183
     */
184
    protected function applyBomStripping(AbstractCsv $csv)
185
    {
186
        if ($this->strip_bom && '' != $csv->getInputBOM()) {
187
            return $this->getStripBomIterator($csv);
188
        }
189
190
        return $csv->getIterator();
191
    }
192
193
    /**
194
     * Return the Iterator without the BOM sequence
195
     *
196
     * @param AbstractCsv $csv
197
     *
198
     * @return Iterator
199
     */
200
    protected function getStripBomIterator(AbstractCsv $csv)
201
    {
202
        $bom_length = mb_strlen($csv->getInputBOM());
203
        $enclosure = $csv->getEnclosure();
204
        $strip_bom = function ($row, $index) use ($bom_length, $enclosure) {
205
            if (0 != $index) {
206
                return $row;
207
            }
208
209
            $row[0] = mb_substr($row[0], $bom_length);
210
            if ($row[0][0] === $enclosure && mb_substr($row[0], -1, 1) === $enclosure) {
211
                $row[0] = mb_substr($row[0], 1, -1);
212
            }
213
214
            return $row;
215
        };
216
217
        return new MapIterator($csv->getIterator(), $strip_bom);
218
    }
219
220
    /**
221
    * Filter the Iterator
222
    *
223
    * @param Iterator $iterator
224
    *
225
    * @return Iterator
226
    */
227
    protected function applyIteratorFilter(Iterator $iterator)
228
    {
229
        $reducer = function ($iterator, $callable) {
230
            return new CallbackFilterIterator($iterator, $callable);
231
        };
232
233
        return array_reduce($this->filters, $reducer, $iterator);
234
    }
235
236
    /**
237
    * Sort the Iterator
238
    *
239
    * @param Iterator $iterator
240
    *
241
    * @return Iterator
242
    */
243
    protected function applyIteratorSortBy(Iterator $iterator)
244
    {
245
        if (!$this->sort_by) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->sort_by of type callable[] is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
246
            return $iterator;
247
        }
248
249
        $obj = new ArrayIterator(iterator_to_array($iterator));
250
        $obj->uasort(function ($row_a, $row_b) {
251
            $res = 0;
252
            foreach ($this->sort_by as $compare) {
253
                if (0 !== ($res = call_user_func($compare, $row_a, $row_b))) {
254
                    break;
255
                }
256
            }
257
258
            return $res;
259
        });
260
261
        return $obj;
262
    }
263
264
    /**
265
     * Convert Iterator to UTF-8 if needed
266
     *
267
     * @param string   $input_encoding
268
     * @param Iterator $iterator
269
     *
270
     * @return Iterator
271
     */
272
    protected function convertToUtf8($input_encoding, Iterator $iterator)
273
    {
274
        if (stripos($input_encoding, 'UTF-8') !== false) {
275
            return $iterator;
276
        }
277
278
        $convert_cell = function ($value) use ($input_encoding) {
279
            return mb_convert_encoding($value, 'UTF-8', $input_encoding);
280
        };
281
282
        $convert_row = function (array $row) use ($convert_cell) {
283
            return array_map($convert_cell, $row);
284
        };
285
286
        return new MapIterator($iterator, $convert_row);
287
    }
288
}
289