Completed
Pull Request — master (#210)
by ignace nyamagana
02:58 queued 01:13
created

RecordSet::getInputEncoding()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 4
rs 10
c 0
b 0
f 0
cc 1
eloc 2
nc 1
nop 0
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 9.0.0
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
declare(strict_types=1);
14
15
namespace League\Csv;
16
17
use CallbackFilterIterator;
18
use Countable;
19
use DomDocument;
20
use Generator;
21
use InvalidArgumentException;
22
use Iterator;
23
use IteratorAggregate;
24
use JsonSerializable;
25
use League\Csv\Config\ValidatorTrait;
26
use LimitIterator;
27
28
/**
29
 *  A class to manage extracting and filtering a CSV
30
 *
31
 * @package League.csv
32
 * @since  3.0.0
33
 *
34
 */
35
class RecordSet implements JsonSerializable, IteratorAggregate, Countable
36
{
37
    use ValidatorTrait;
38
39
    /**
40
     * Charset Encoding for the CSV
41
     *
42
     * This information is used when converting the CSV to XML or JSON
43
     *
44
     * @var string
45
     */
46
    protected $input_encoding = 'UTF-8';
47
48
    /**
49
     * The CSV iterator result
50
     *
51
     * @var Iterator
52
     */
53
    protected $iterator;
54
55
    /**
56
     * The CSV header
57
     *
58
     * @var array
59
     */
60
    protected $headers = [];
61
62
    /**
63
     * New instance
64
     *
65
     * @param Iterator $iterator a CSV iterator created from Statement
66
     * @param array    $headers  the CSV headers
67
     */
68
    public function __construct(Iterator $iterator, array $headers = [])
69
    {
70
        $this->iterator = $iterator;
71
        $this->headers = $headers;
72
    }
73
74
    /**
75
     * @inheritdoc
76
     */
77
    public function __destruct()
78
    {
79
        $this->iterator = null;
80
    }
81
82
    /**
83
     * Gets the source CSV encoding charset
84
     *
85
     * @return string
86
     */
87
    public function getInputEncoding(): string
88
    {
89
        return $this->input_encoding;
90
    }
91
92
    /**
93
     * Sets the CSV encoding charset
94
     *
95
     * @param string $str
96
     *
97
     * @return static
98
     */
99
    public function setInputEncoding(string $str): self
100
    {
101
        $str = str_replace('_', '-', $str);
102
        $str = filter_var($str, FILTER_SANITIZE_STRING, ['flags' => FILTER_FLAG_STRIP_LOW | FILTER_FLAG_STRIP_HIGH]);
103
        if (empty($str)) {
104
            throw new InvalidArgumentException('you should use a valid charset');
105
        }
106
        $this->input_encoding = strtoupper($str);
107
108
        return $this;
109
    }
110
111
    /**
112
     * Returns a HTML table representation of the CSV Table
113
     *
114
     * @param string $class_attr optional classname
115
     *
116
     * @return string
117
     */
118
    public function toHTML(string $class_attr = 'table-csv-data'): string
119
    {
120
        $doc = $this->toXML('table', 'tr', 'td');
121
        $doc->documentElement->setAttribute('class', $class_attr);
122
123
        return $doc->saveHTML($doc->documentElement);
124
    }
125
126
    /**
127
     * Transforms a CSV into a XML
128
     *
129
     * @param string $root_name XML root node name
130
     * @param string $row_name  XML row node name
131
     * @param string $cell_name XML cell node name
132
     *
133
     * @return DomDocument
134
     */
135
    public function toXML(string $root_name = 'csv', string $row_name = 'row', string $cell_name = 'cell'): DomDocument
136
    {
137
        $doc = new DomDocument('1.0', 'UTF-8');
138
        $root = $doc->createElement($root_name);
139
        foreach ($this->convertToUtf8($this->iterator) as $row) {
140
            $rowElement = $doc->createElement($row_name);
141
            array_walk($row, function ($value) use (&$rowElement, $doc, $cell_name) {
142
                $content = $doc->createTextNode($value);
143
                $cell = $doc->createElement($cell_name);
144
                $cell->appendChild($content);
145
                $rowElement->appendChild($cell);
146
            });
147
            $root->appendChild($rowElement);
148
        }
149
        $doc->appendChild($root);
150
151
        return $doc;
152
    }
153
154
    /**
155
     * Convert Csv file into UTF-8
156
     *
157
     * @param Iterator $iterator
158
     *
159
     * @return Iterator
160
     */
161
    protected function convertToUtf8(Iterator $iterator): Iterator
162
    {
163
        if (stripos($this->input_encoding, 'UTF-8') !== false) {
164
            return $iterator;
165
        }
166
167
        $convert_cell = function ($value) {
168
            return mb_convert_encoding((string) $value, 'UTF-8', $this->input_encoding);
169
        };
170
171
        $convert_row = function (array $row) use ($convert_cell) {
172
            $res = [];
173
            foreach ($row as $key => $value) {
174
                $res[$convert_cell($key)] = $convert_cell($value);
175
            }
176
177
            return $res;
178
        };
179
180
        return new MapIterator($iterator, $convert_row);
181
    }
182
183
    /**
184
     * @inheritdoc
185
     */
186
    public function getIterator(): Iterator
187
    {
188
        return $this->iterator;
189
    }
190
191
    /**
192
     * @inheritdoc
193
     */
194
    public function count()
195
    {
196
        return iterator_count($this->iterator);
197
    }
198
199
    /**
200
     * @inheritdoc
201
     */
202
    public function jsonSerialize()
203
    {
204
        return iterator_to_array($this->convertToUtf8($this->iterator), false);
205
    }
206
207
    /**
208
     * Returns a sequential array of all CSV lines
209
     *
210
     * @return array
211
     */
212
    public function fetchAll(): array
213
    {
214
        return iterator_to_array($this->iterator, false);
215
    }
216
217
    /**
218
     * Returns a single row from the CSV
219
     *
220
     * By default if no offset is provided the first row of the CSV is selected
221
     *
222
     * @param int $offset the CSV row offset
223
     *
224
     * @return array
225
     */
226
    public function fetchOne(int $offset = 0): array
227
    {
228
        $offset = $this->filterInteger($offset, 0, 'the submitted offset is invalid');
229
        $it = new LimitIterator($this->iterator, $offset, 1);
230
        $it->rewind();
231
232
        return (array) $it->current();
233
    }
234
235
    /**
236
     * Returns the next value from a single CSV column
237
     *
238
     * By default if no column index is provided the first column of the CSV is selected
239
     *
240
     * @param string|int $column CSV column index
241
     *
242
     * @return Iterator
243
     */
244
    public function fetchColumn($column = 0): Iterator
245
    {
246
        $column = $this->getFieldIndex($column, 'the column index value is invalid');
247
        $filter = function (array $row) use ($column) {
248
            return isset($row[$column]);
249
        };
250
251
        $select = function ($row) use ($column) {
252
            return $row[$column];
253
        };
254
255
        return new MapIterator(new CallbackFilterIterator($this->iterator, $filter), $select);
256
    }
257
258
    /**
259
     * Filter a field name against the CSV header if any
260
     *
261
     * @param string|int $field         the field name or the field index
262
     * @param string     $error_message the associated error message
263
     *
264
     * @throws InvalidArgumentException if the field is invalid
265
     *
266
     * @return string|int
267
     */
268
    protected function getFieldIndex($field, $error_message)
269
    {
270
        if (false !== array_search($field, $this->headers, true)) {
271
            return $field;
272
        }
273
274
        $index = $this->filterInteger($field, 0, $error_message);
275
        if (empty($this->headers)) {
276
            return $index;
277
        }
278
279
        if (false !== ($index = array_search($index, array_flip($this->headers), true))) {
280
            return $index;
281
        }
282
283
        throw new InvalidArgumentException($error_message);
284
    }
285
286
    /**
287
     * Fetches the next key-value pairs from a result set (first
288
     * column is the key, second column is the value).
289
     *
290
     * By default if no column index is provided:
291
     * - the first CSV column is used to provide the keys
292
     * - the second CSV column is used to provide the value
293
     *
294
     * @param string|int $offset_index The column index to serve as offset
295
     * @param string|int $value_index  The column index to serve as value
296
     *
297
     * @return Generator
298
     */
299
    public function fetchPairs($offset_index = 0, $value_index = 1): Generator
300
    {
301
        $offset = $this->getFieldIndex($offset_index, 'the offset index value is invalid');
302
        $value = $this->getFieldIndex($value_index, 'the value index value is invalid');
303
        $filter = function ($row) use ($offset) {
304
            return isset($row[$offset]);
305
        };
306
307
        $select = function ($row) use ($offset, $value) {
308
            return [$row[$offset], $row[$value] ?? null];
309
        };
310
311
        $it = new MapIterator(new CallbackFilterIterator($this->iterator, $filter), $select);
312
        foreach ($it as $row) {
313
            yield $row[0] => $row[1];
314
        }
315
    }
316
}
317