Completed
Push — master ( d97897...85851c )
by Harry
03:38
created

CsvParser::parseHeaderRow()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 8
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 2

Importance

Changes 2
Bugs 0 Features 1
Metric Value
c 2
b 0
f 1
dl 0
loc 8
ccs 5
cts 5
cp 1
rs 9.4285
cc 2
eloc 5
nc 2
nop 1
crap 2
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
    /** @var array */
33
    private $headerRow = [];
34
35
    /**
36
     * @param CsvFormatInterface $csvFormat
37
     */
38 9
    public function __construct(CsvFormatInterface $csvFormat)
39
    {
40 9
        $this->csvFormat = $csvFormat;
41 9
    }
42
43
    /**
44
     * @param StreamInterface $stream
45
     *
46
     * @return Iterator
47
     */
48 9
    public function parse(StreamInterface $stream)
49
    {
50
        $config = [
51 9
            CsvConfiguration::OPTION_DELIMITER    => $this->csvFormat->getDelimiter(),
52 9
            CsvConfiguration::OPTION_QUOTE        => $this->csvFormat->getQuoteCharacter(),
53 9
            CsvConfiguration::OPTION_ESCAPE       => $this->csvFormat->getEscapeCharacter(),
54 9
            CsvConfiguration::OPTION_DOUBLE_QUOTE => $this->csvFormat->isDoubleQuote(),
55 9
            CsvConfiguration::OPTION_NEW_LINES    => [$this->csvFormat->getLineTerminator()],
56 9
            CsvConfiguration::OPTION_NULL         => $this->csvFormat->getNullOutput(),
57 9
            CsvConfiguration::OPTION_ENCODING     => $this->csvFormat->getEncoding(),
58
        ];
59 9
        if (!is_null($this->csvFormat->getBom())) {
60 1
            $config[CsvConfiguration::OPTION_BOMS] = [$this->csvFormat->getBom()];
61
        }
62
63 9
        $configuration = new CsvConfiguration($config);
64 9
        $tokeniser = new StreamTokeniser($configuration, $stream);
65 9
        $parser = new Parser();
66 9
        return $this->parseIterator(
67 9
            $parser->parse($tokeniser->getTokens())
68
        );
69
    }
70
71
    /**
72
     * Parse a supplied iterator
73
     *
74
     * @param Iterator $iterator
75
     *
76
     * @return Iterator
77
     */
78 9
    private function parseIterator(Iterator $iterator)
79
    {
80 9
        $iterator = $this->parseHeaderRow($iterator);
81 9
        if ($this->csvFormat->getDataStart() > 1 || $this->csvFormat->getLimit() !== -1) {
82 5
            $iterator = new LimitIterator(
83
                $iterator,
84 5
                max(0, $this->csvFormat->getDataStart() - 1),
85 5
                $this->csvFormat->getLimit()
86
            );
87
        }
88 9
        return $iterator;
89
    }
90
91
    /**
92
     * @param Iterator $iterator
93
     *
94
     * @return Iterator
95
     */
96 9
    private function parseHeaderRow(Iterator $iterator)
97
    {
98 9
        if ($this->csvFormat->hasHeaderRow()) {
99 3
            $iterator = new CallbackFilterIterator($iterator, [$this, 'handleHeaderRow']);
100 3
            $iterator = new MapIterator($iterator, [$this, 'mapHeaders']);
101
        }
102 9
        return $iterator;
103
    }
104
105
    /**
106
     * Parse the rows looking for the header row and storing it locally
107
     *
108
     * @param ArrayIterator $current
109
     * @param int           $key
110
     *
111
     * @return bool
112
     */
113 3
    public function handleHeaderRow(ArrayIterator $current, $key)
114
    {
115 3
        if ($key == $this->csvFormat->getHeaderRow() - 1) {
116 3
            $arr = iterator_to_array($current);
117 3
            if (count($arr) > 1 || strlen($arr[0]) > 0) {
118 2
                $this->headerRow = $arr;
119
            }
120
        }
121 3
        return true;
122
    }
123
124
    /**
125
     * Map any headers found onto each element in the data
126
     *
127
     * @param ArrayIterator $current
128
     *
129
     * @return ArrayIterator
130
     */
131 3
    public function mapHeaders(ArrayIterator $current)
132
    {
133 3
        if (count($this->headerRow) > 0) {
134 2
            if (count($this->headerRow) !== $current->count()) {
135 1
                throw new RuntimeException(
136 1
                    "The number of entries in: " . implode(',', iterator_to_array($current)) .
137 1
                    " does not match the header: " . implode(',', $this->headerRow)
138
                );
139
            }
140 1
            return new ArrayIterator(array_combine(
141 1
                $this->headerRow,
142
                iterator_to_array($current)
143
            ));
144
        }
145 1
        return $current;
146
    }
147
}
148