Completed
Pull Request — master (#210)
by ignace nyamagana
02:28
created

RecordSet::toHTML()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 7
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

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