Completed
Push — master ( e42624...c35825 )
by Harry
9s
created

CsvParser::parseHeaderRow()   C

Complexity

Conditions 7
Paths 2

Size

Total Lines 33
Code Lines 19

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 56

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 33
ccs 0
cts 0
cp 0
rs 6.7272
cc 7
eloc 19
nc 2
nop 1
crap 56
1
<?php
2
/**
3
 * This file is part of graze/data-file
4
 *
5
 * Copyright (c) 2016 Nature Delivered Ltd. <https://www.graze.com>
6
 *
7
 * For the full copyright and license information, please view the LICENSE
8
 * file that was distributed with this source code.
9
 *
10
 * @license https://github.com/graze/data-file/blob/master/LICENSE.md
11
 * @link    https://github.com/graze/data-file
12
 */
13
14
namespace Graze\DataFile\Format\Parser;
15
16
use ArrayIterator;
17
use CallbackFilterIterator;
18
use Graze\CsvToken\Csv\CsvConfiguration;
19
use Graze\CsvToken\Parser;
20
use Graze\CsvToken\Tokeniser\StreamTokeniser;
21
use Graze\DataFile\Format\CsvFormatInterface;
22
use Graze\DataFile\Helper\MapIterator;
23
use Iterator;
24
use LimitIterator;
25
use Psr\Http\Message\StreamInterface;
26
use RuntimeException;
27
28
class CsvParser implements ParserInterface
29
{
30
    /** @var CsvFormatInterface */
31
    private $csvFormat;
32 5
    /** @var array */
33
    private $headerRow = [];
34 5
35 5
    /**
36
     * @param CsvFormatInterface $csvFormat
37
     */
38
    public function __construct(CsvFormatInterface $csvFormat)
39
    {
40
        $this->csvFormat = $csvFormat;
41
    }
42 5
43
    /**
44 5
     * @param StreamInterface $stream
45 5
     *
46 5
     * @return Iterator
47 5
     */
48 5
    public function parse(StreamInterface $stream)
49 5
    {
50 5
        $configuration = new CsvConfiguration([
51 5
            CsvConfiguration::OPTION_DELIMITER    => $this->csvFormat->getDelimiter(),
52 5
            CsvConfiguration::OPTION_QUOTE        => $this->csvFormat->getQuoteCharacter(),
53 5
            CsvConfiguration::OPTION_ESCAPE       => $this->csvFormat->getEscapeCharacter(),
54 5
            CsvConfiguration::OPTION_DOUBLE_QUOTE => $this->csvFormat->isDoubleQuote(),
55 5
            CsvConfiguration::OPTION_NEW_LINE     => $this->csvFormat->getLineTerminator(),
56 5
            CsvConfiguration::OPTION_NULL         => $this->csvFormat->getNullOutput(),
57
        ]);
58
        $tokeniser = new StreamTokeniser($configuration, $stream);
59
        $parser = new Parser();
60
        return $this->parseIterator(
61
            $parser->parse($tokeniser->getTokens())
62
        );
63
    }
64
65
    /**
66 5
     * Parse a supplied iterator
67
     *
68 5
     * @param Iterator $iterator
69 2
     *
70 2
     * @return Iterator
71 5
     */
72
    private function parseIterator(Iterator $iterator)
73
    {
74
        $iterator = $this->parseHeaderRow($iterator);
75
        if ($this->csvFormat->getDataStart() > 1 || $this->csvFormat->getLimit() !== -1) {
76
            $iterator = new LimitIterator(
77
                $iterator,
78
                max(0, $this->csvFormat->getDataStart() - 1),
79
                $this->csvFormat->getLimit()
80
            );
81
        }
82
        return $iterator;
83
    }
84
85
    /**
86
     * @param Iterator $iterator
87
     *
88
     * @return Iterator
89
     */
90
    private function parseHeaderRow(Iterator $iterator)
91
    {
92
        if ($this->csvFormat->hasHeaderRow()) {
93
            // use a callback iterator to get just the header row without having to rewind the source
94
            $iterator = new CallbackFilterIterator($iterator, function ($current, $key) {
95
                if ($key == $this->csvFormat->getHeaderRow() - 1) {
96
                    $arr = iterator_to_array($current);
97
                    if (count($arr) > 1 || strlen($arr[0]) > 0) {
98
                        $this->headerRow = $arr;
99
                    }
100
                }
101
                return true;
102
            });
103
104
            // map the header row onto each row if applicable
105
            $iterator = new MapIterator($iterator, function ($current) {
106
                if (count($this->headerRow) > 0) {
107
                    if (count($this->headerRow) !== count($current)) {
108
                        throw new RuntimeException(
109
                            "The number of entries in: " . implode(',', iterator_to_array($current)) .
110
                            " does not match the header: " . implode(',', $this->headerRow)
111
                        );
112
                    }
113
                    return new ArrayIterator(array_combine(
114
                        $this->headerRow,
115
                        iterator_to_array($current)
116
                    ));
117
                }
118
                return $current;
119
            });
120
        }
121
        return $iterator;
122
    }
123
}
124