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

RecordSet::toHTML()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 7
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 7
ccs 4
cts 4
cp 1
rs 9.4285
c 0
b 0
f 0
cc 1
eloc 4
nc 1
nop 1
crap 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 DOMElement;
21
use Generator;
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 $conversion_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 $header = [];
61
62
    /**
63
     * New instance
64
     *
65
     * @param Iterator $iterator a CSV iterator created from Statement
66
     * @param array    $header   the CSV header
67
     */
68 108
    public function __construct(Iterator $iterator, array $header = [])
69
    {
70 108
        $this->iterator = $iterator;
71 108
        $this->header = $header;
72 108
    }
73
74
    /**
75
     * @inheritdoc
76
     */
77 108
    public function __destruct()
78
    {
79 108
        $this->iterator = null;
80 108
    }
81
82
    /**
83
     * Returns the column header associate with the RecordSet
84
     *
85
     * @return string[]
86
     */
87 6
    public function getHeader()
88
    {
89 6
        return $this->header;
90
    }
91
92
    /**
93
     * Gets the source CSV encoding charset
94
     *
95
     * @return string
96
     */
97 2
    public function getConversionInputEncoding(): string
98
    {
99 2
        return $this->conversion_input_encoding;
100
    }
101
102
    /**
103
     * Sets the CSV encoding charset
104
     *
105
     * @param string $str
106
     *
107
     * @return static
108
     */
109 6
    public function setConversionInputEncoding(string $str): self
110
    {
111 6
        $str = str_replace('_', '-', $str);
112 6
        $str = filter_var($str, FILTER_SANITIZE_STRING, ['flags' => FILTER_FLAG_STRIP_LOW | FILTER_FLAG_STRIP_HIGH]);
113 6
        if (empty($str)) {
114 2
            throw new Exception('you should use a valid charset');
115
        }
116 4
        $this->conversion_input_encoding = strtoupper($str);
117
118 4
        return $this;
119
    }
120
121
    /**
122
     * Returns a HTML table representation of the CSV Table
123
     *
124
     * @param string $class_attr optional classname
125
     *
126
     * @return string
127
     */
128 2
    public function toHTML(string $class_attr = 'table-csv-data'): string
129
    {
130 2
        $doc = $this->toXML('table', 'tr', 'td');
131 2
        $doc->documentElement->setAttribute('class', $class_attr);
132
133 2
        return $doc->saveHTML($doc->documentElement);
134
    }
135
136
    /**
137
     * Transforms a CSV into a XML
138
     *
139
     * @param string $root_name XML root node name
140
     * @param string $row_name  XML row node name
141
     * @param string $cell_name XML cell node name
142
     *
143
     * @return DOMDocument
144
     */
145 4
    public function toXML(string $root_name = 'csv', string $row_name = 'row', string $cell_name = 'cell'): DOMDocument
146
    {
147 4
        $doc = new DOMDocument('1.0', 'UTF-8');
148 4
        $root = $doc->createElement($root_name);
149 4
        if (!empty($this->header)) {
150 2
            $rowElement = $this->toDOMNode($doc, $this->header, $row_name, $cell_name);
151 2
            $root->appendChild($rowElement);
152
        }
153 4
        foreach ($this->convertToUtf8($this->iterator) as $row) {
154 4
            $rowElement = $this->toDOMNode($doc, $row, $row_name, $cell_name);
155 4
            $root->appendChild($rowElement);
156
        }
157 4
        $doc->appendChild($root);
158
159 4
        return $doc;
160
    }
161
162
    /**
163
     * convert a Record into a DOMNode
164
     *
165
     * @param DOMDocument $doc       The DOMDocument
166
     * @param array       $row       The CSV record
167
     * @param string      $row_name  XML row node name
168
     * @param string      $cell_name XML cell node name
169
     *
170
     * @return DOMElement
171
     */
172 4
    protected function toDOMNode(DOMDocument $doc, array $row, string $row_name, string $cell_name): DOMElement
173
    {
174 4
        $rowElement = $doc->createElement($row_name);
175 4
        foreach ($row as $value) {
176 4
            $content = $doc->createTextNode($value);
177 4
            $cell = $doc->createElement($cell_name);
178 4
            $cell->appendChild($content);
179 4
            $rowElement->appendChild($cell);
180
        }
181
182 4
        return $rowElement;
183
    }
184
185
    /**
186
     * Convert Csv file into UTF-8
187
     *
188
     * @param Iterator $iterator
189
     *
190
     * @return Iterator
191
     */
192 6
    protected function convertToUtf8(Iterator $iterator): Iterator
193
    {
194 6
        if (stripos($this->conversion_input_encoding, 'UTF-8') !== false) {
195 4
            return $iterator;
196
        }
197
198
        $convert_cell = function ($value) {
199 2
            return mb_convert_encoding((string) $value, 'UTF-8', $this->conversion_input_encoding);
200 2
        };
201
202
        $convert_row = function (array $row) use ($convert_cell) {
203 2
            $res = [];
204 2
            foreach ($row as $key => $value) {
205 2
                $res[$convert_cell($key)] = $convert_cell($value);
206
            }
207
208 2
            return $res;
209 2
        };
210
211 2
        return new MapIterator($iterator, $convert_row);
212
    }
213
214
    /**
215
     * @inheritdoc
216
     */
217 2
    public function getIterator(): Iterator
218
    {
219 2
        return $this->iterator;
220
    }
221
222
    /**
223
     * @inheritdoc
224
     */
225 2
    public function count()
226
    {
227 2
        return iterator_count($this->iterator);
228
    }
229
230
    /**
231
     * @inheritdoc
232
     */
233 2
    public function jsonSerialize()
234
    {
235 2
        return iterator_to_array($this->convertToUtf8($this->iterator), false);
236
    }
237
238
    /**
239
     * Returns a sequential array of all CSV lines
240
     *
241
     * @return array
242
     */
243 64
    public function fetchAll(): array
244
    {
245 64
        return iterator_to_array($this->iterator, false);
246
    }
247
248
    /**
249
     * Returns a single row from the CSV
250
     *
251
     * By default if no offset is provided the first row of the CSV is selected
252
     *
253
     * @param int $offset the CSV row offset
254
     *
255
     * @return array
256
     */
257 6
    public function fetchOne(int $offset = 0): array
258
    {
259 6
        $offset = $this->filterInteger($offset, 0, 'the submitted offset is invalid');
260 4
        $it = new LimitIterator($this->iterator, $offset, 1);
261 4
        $it->rewind();
262
263 4
        return (array) $it->current();
264
    }
265
266
    /**
267
     * Returns the next value from a single CSV column
268
     *
269
     * By default if no column index is provided the first column of the CSV is selected
270
     *
271
     * @param string|int $column CSV column index
272
     *
273
     * @return Iterator
274
     */
275 14
    public function fetchColumn($column = 0): Iterator
276
    {
277 14
        $column = $this->getFieldIndex($column, 'the column index value is invalid');
278
        $filter = function (array $row) use ($column) {
279 12
            return isset($row[$column]);
280 12
        };
281
282
        $select = function ($row) use ($column) {
283 8
            return $row[$column];
284 12
        };
285
286 12
        return new MapIterator(new CallbackFilterIterator($this->iterator, $filter), $select);
287
    }
288
289
    /**
290
     * Filter a field name against the CSV header if any
291
     *
292
     * @param string|int $field         the field name or the field index
293
     * @param string     $error_message the associated error message
294
     *
295
     * @throws Exception if the field is invalid
296
     *
297
     * @return string|int
298
     */
299 22
    protected function getFieldIndex($field, $error_message)
300
    {
301 22
        if (false !== array_search($field, $this->header, true) || is_string($field)) {
302 2
            return $field;
303
        }
304
305 20
        $index = $this->filterInteger($field, 0, $error_message);
306 20
        if (empty($this->header)) {
307 16
            return $index;
308
        }
309
310 4
        if (false !== ($index = array_search($index, array_flip($this->header), true))) {
311 2
            return $index;
312
        }
313
314 2
        throw new Exception($error_message);
315
    }
316
317
    /**
318
     * Fetches the next key-value pairs from a result set (first
319
     * column is the key, second column is the value).
320
     *
321
     * By default if no column index is provided:
322
     * - the first CSV column is used to provide the keys
323
     * - the second CSV column is used to provide the value
324
     *
325
     * @param string|int $offset_index The column index to serve as offset
326
     * @param string|int $value_index  The column index to serve as value
327
     *
328
     * @return Generator
329
     */
330 8
    public function fetchPairs($offset_index = 0, $value_index = 1): Generator
331
    {
332 8
        $offset = $this->getFieldIndex($offset_index, 'the offset index value is invalid');
333 8
        $value = $this->getFieldIndex($value_index, 'the value index value is invalid');
334
        $filter = function ($row) use ($offset) {
335 8
            return isset($row[$offset]);
336 8
        };
337
338 8
        $select = function ($row) use ($offset, $value) {
339 6
            return [$row[$offset], $row[$value] ?? null];
340 8
        };
341
342 8
        $it = new MapIterator(new CallbackFilterIterator($this->iterator, $filter), $select);
343 8
        foreach ($it as $row) {
344 6
            yield $row[0] => $row[1];
345
        }
346 8
    }
347
}
348