Completed
Push — master ( 2a6d3f...af3067 )
by ignace nyamagana
02:36
created

Converter::xmlAttributes()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 9
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 6
nc 1
nop 2
dl 0
loc 9
ccs 6
cts 6
cp 1
crap 1
rs 9.6666
c 0
b 0
f 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 DOMDocument;
18
use DOMElement;
19
use Iterator;
20
use League\Csv\Exception\InvalidArgumentException;
21
use Traversable;
22
23
/**
24
 * A class to convert CSV records into a DOMDOcument object
25
 *
26
 * @package League.csv
27
 * @since   9.0.0
28
 * @author  Ignace Nyamagana Butera <[email protected]>
29
 */
30
class Converter
31
{
32
    use ValidatorTrait;
33
34
    /**
35
     * XML Root name
36
     *
37
     * @var string
38
     */
39
    protected $root_name = 'csv';
40
41
    /**
42
     * XML Node name
43
     *
44
     * @var string
45
     */
46
    protected $record_name = 'row';
47
48
    /**
49
     * XML Item name
50
     *
51
     * @var string
52
     */
53
    protected $item_name = 'cell';
54
55
    /**
56
     * XML column attribute name
57
     *
58
     * @var string
59
     */
60
    protected $column_attr = '';
61
62
    /**
63
     * XML offset attribute name
64
     *
65
     * @var string
66
     */
67
    protected $offset_attr = '';
68
69
    /**
70
     * Tell whether to preserve offset for json
71
     *
72
     * @var bool
73
     */
74
    protected $json_preserve_offset = false;
75
76
    /**
77
     * json_encode options
78
     *
79
     * @var int
80
     */
81
    protected $json_options = 0;
82
83
    /**
84
     * json_encode depth
85
     *
86
     * @var int
87
     */
88
    protected $json_depth = 512;
89
90
    /**
91
     * Charset Encoding for the CSV
92
     *
93
     * This information is used when converting the CSV to XML or JSON
94
     *
95
     * @var string
96
     */
97
    protected $input_encoding = 'UTF-8';
98
99
    /**
100
     * Conversion method list
101
     *
102
     * @var array
103
     */
104
    protected $encoder = [
105
        'item' => [
106
            true => 'itemToElementWithAttribute',
107
            false => 'itemToElement',
108
        ],
109
        'record' => [
110
            true => 'recordToElementWithAttribute',
111
            false => 'recordToElement',
112
        ],
113
    ];
114
115
    /**
116
     * Sets the CSV encoding charset
117
     *
118
     * @param string $input_encoding
119
     *
120
     * @throws InvalidArgumentException if the charset is empty
121
     *
122
     * @return static
123
     */
124 12
    public function inputEncoding(string $input_encoding): self
125
    {
126 12
        $input_encoding = $this->filterElementName($input_encoding, 'input_encoding', __METHOD__);
127 10
        $clone = clone $this;
128 10
        $clone->input_encoding = strtoupper(str_replace('_', '-', $input_encoding));
129
130 10
        return $clone;
131
    }
132
133
    /**
134
     * Filter XML element name
135
     *
136
     * @param string $value  Element name
137
     * @param string $type   Element type
138
     * @param string $method Method call
139
     *
140
     * @throws InvalidArgumentException If the Element name is empty
141
     *
142
     * @return string
143
     */
144 14
    protected function filterElementName(string $value, string $type, string $method): string
145
    {
146 14
        $value = filter_var($value, FILTER_SANITIZE_STRING, ['flags' => FILTER_FLAG_STRIP_LOW | FILTER_FLAG_STRIP_HIGH]);
147 14
        $value = trim($value);
148 14
        if ('' !== $value) {
149 10
            return $value;
150
        }
151
152 4
        throw new InvalidArgumentException(sprintf('%s: %s must be a non empty string', $method, $type));
153
    }
154
155
    /**
156
     * XML Element names setter
157
     *
158
     * @param string $root_name   XML root element name
159
     * @param string $record_name XML record element name
160
     * @param string $item_name   XML record item element name
161
     *
162
     * @return self
163
     */
164 8
    public function xmlElements(
165
        string $root_name = 'csv',
166
        string $record_name = 'row',
167
        string $item_name = 'cell'
168
    ): self {
169 8
        $clone = clone $this;
170 8
        $clone->root_name = $this->filterElementName($root_name, 'root_name', __METHOD__);
171 6
        $clone->record_name = $this->filterElementName($record_name, 'record_name', __METHOD__);
172 6
        $clone->item_name = $this->filterElementName($item_name, 'item_name', __METHOD__);
173
174 6
        return $clone;
175
    }
176
177
    /**
178
     * XML Element attributes name setter
179
     *
180
     * @param string $offset_attr XML record element offset attribute name
181
     * @param string $column_attr XML record item field attribute name
182
     *
183
     * @return self
184
     */
185 4
    public function xmlAttributes(string $offset_attr = '', string $column_attr = ''): self
186
    {
187 4
        $clone = clone $this;
188 4
        $options = ['flags' => FILTER_FLAG_STRIP_LOW | FILTER_FLAG_STRIP_HIGH];
189 4
        $clone->column_attr = trim(filter_var($column_attr, FILTER_SANITIZE_STRING, $options));
190 4
        $clone->offset_attr = trim(filter_var($offset_attr, FILTER_SANITIZE_STRING, $options));
191
192 4
        return $clone;
193
    }
194
195
    /**
196
     * Json encode Options
197
     *
198
     * @param bool $preserve_offset
199
     * @param int  $options
200
     * @param int  $depth
201
     *
202
     * @return self
203
     */
204 4
    public function jsonOptions(bool $preserve_offset, int $options = 0, int $depth = 512): self
205
    {
206 4
        $clone = clone $this;
207 4
        $clone->json_preserve_offset = $preserve_offset;
208 4
        $clone->json_options = $options;
209 4
        $clone->json_depth = $depth;
210
211 4
        return $clone;
212
    }
213
214
    /**
215
     * Convert Csv file into UTF-8
216
     *
217
     * @param array|Traversable $records the CSV records collection
218
     *
219
     * @return array|Iterator
220
     */
221 6
    protected function convertToUtf8($records)
222
    {
223 6
        if (stripos($this->input_encoding, 'UTF-8') !== false) {
224 2
            return $records;
225
        }
226
227
        $walker = function (&$value, &$offset) {
228 4
            $value = mb_convert_encoding((string) $value, 'UTF-8', $this->input_encoding);
229 4
            $offset = mb_convert_encoding((string) $offset, 'UTF-8', $this->input_encoding);
230 4
        };
231
232 4
        $convert = function (array $record) use ($walker): array {
233 4
            array_walk($record, $walker);
234 4
            return $record;
235 4
        };
236
237 4
        return is_array($records) ? array_map($convert, $records) : new MapIterator($records, $convert);
238
    }
239
240
    /**
241
     * Convert an Record collection into a DOMDocument
242
     *
243
     * @param array|Traversable $records the CSV records collection
244
     *
245
     * @return DOMDocument
246
     */
247 4
    public function toXML($records): DOMDocument
248
    {
249 4
        $item_encoder = $this->encoder['item']['' !== $this->column_attr];
250 4
        $record_encoder = $this->encoder['record']['' !== $this->offset_attr];
251 4
        $doc = new DOMDocument('1.0', 'UTF-8');
252 4
        $root = $doc->createElement($this->root_name);
253 4
        $records = $this->convertToUtf8($this->filterIterable($records, __METHOD__));
254 4
        foreach ($records as $offset => $record) {
255 4
            $node = $this->{$record_encoder}($doc, $record, $item_encoder, $offset);
256 4
            $root->appendChild($node);
257
        }
258 4
        $doc->appendChild($root);
259
260 4
        return $doc;
261
    }
262
263
    /**
264
     * Convert an Record collection into a Json string
265
     *
266
     * @param array|Traversable $records the CSV records collection
267
     *
268
     * @return string
269
     */
270 2
    public function toJson($records): string
271
    {
272 2
        $records = $this->convertToUtf8($this->filterIterable($records, __METHOD__));
273
274 2
        if (!is_array($records)) {
275 2
            return json_encode(
276 2
                iterator_to_array($records, $this->json_preserve_offset),
277 2
                $this->json_options,
278 2
                $this->json_depth
279
            );
280
        }
281
282 2
        return json_encode(
283 2
            $this->json_preserve_offset ? $records : array_values($records),
284 2
            $this->json_options,
285 2
            $this->json_depth
286
        );
287
    }
288
289
    /**
290
     * Convert an Record collection into a HTML table
291
     *
292
     * @param array|Traversable $records the CSV records collection
293
     *
294
     * @return DOMDocument
295
     */
296 2
    public function toHTML($records, string $class_attr = 'table-csv-data'): string
297
    {
298 2
        $doc = $this->xmlElements('table', 'tr', 'td')->toXML($records);
299 2
        $doc->documentElement->setAttribute('class', $class_attr);
300
301 2
        return $doc->saveHTML($doc->documentElement);
302
    }
303
304
    /**
305
     * Convert a CSV record into a DOMElement and
306
     * adds its offset as DOMElement attribute
307
     *
308
     * @param DOMDocument $doc
309
     * @param array       $record       CSV record
310
     * @param string      $item_encoder CSV Cell encoder method name
311
     * @param int         $offset       CSV record offset
312
     *
313
     * @return DOMElement
314
     */
315 2
    protected function recordToElementWithAttribute(DOMDocument $doc, array $record, string $item_encoder, int $offset): DOMElement
316
    {
317 2
        $node = $this->recordToElement($doc, $record, $item_encoder);
318 2
        $node->setAttribute($this->offset_attr, (string) $offset);
319
320 2
        return $node;
321
    }
322
323
    /**
324
     * Convert a CSV record into a DOMElement
325
     *
326
     * @param DOMDocument $doc
327
     * @param array       $record       CSV record
328
     * @param string      $item_encoder CSV Cell encoder method name
329
     *
330
     * @return DOMElement
331
     */
332 4
    protected function recordToElement(DOMDocument $doc, array $record, string $item_encoder): DOMElement
333
    {
334 4
        $node = $doc->createElement($this->record_name);
335 4
        foreach ($record as $name => $value) {
336 4
            $item = $this->{$item_encoder}($doc, $value, $name);
337 4
            $node->appendChild($item);
338
        }
339
340 4
        return $node;
341
    }
342
343
    /**
344
     * Convert Cell to Item.
345
     *
346
     * Convert the CSV item into a DOMElement and adds the item offset
347
     * as attribute to the returned DOMElement
348
     *
349
     * @param DOMDocument $doc
350
     * @param string      $value Record item value
351
     * @param int|string  $name  Record item offset
352
     *
353
     * @return DOMElement
354
     */
355 2
    protected function itemToElementWithAttribute(DOMDocument $doc, string $value, $name): DOMElement
356
    {
357 2
        $item = $this->itemToElement($doc, $value);
358 2
        $item->setAttribute($this->column_attr, (string) $name);
359
360 2
        return $item;
361
    }
362
363
    /**
364
     * Convert Cell to Item
365
     *
366
     * @param DOMDocument $doc
367
     * @param string      $value Record item value
368
     *
369
     * @return DOMElement
370
     */
371 4
    protected function itemToElement(DOMDocument $doc, string $value): DOMElement
372
    {
373 4
        $item = $doc->createElement($this->item_name);
374 4
        $item->appendChild($doc->createTextNode($value));
375
376 4
        return $item;
377
    }
378
}
379