Completed
Push — master ( 461219...630231 )
by Harry
04:03
created

CsvFormatter::encode()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 1
Bugs 0 Features 1
Metric Value
c 1
b 0
f 1
dl 0
loc 4
ccs 2
cts 2
cp 1
rs 10
cc 1
eloc 2
nc 1
nop 1
crap 1
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\Formatter;
15
16
use Graze\DataFile\Format\CsvFormatInterface;
17
use Graze\DataFile\Format\Processor\BoolProcessor;
18
use Graze\DataFile\Format\Processor\DateTimeProcessor;
19
use Graze\DataFile\Format\Processor\ObjectToStringProcessor;
20
use Graze\DataFile\Format\Processor\RowProcessor;
21
use InvalidArgumentException;
22
use Traversable;
23
24
class CsvFormatter implements FormatterInterface
25
{
26
    use RowProcessor;
27
    use InvokeFormatter;
28
    use TraversableTrait;
29
30
    /** @var CsvFormatInterface */
31
    private $csvFormat;
32
    /** @var string[] */
33
    private $escapeChars;
34
    /** @var string[] */
35
    private $replaceChars;
36
    /** @var string */
37
    private $initial;
38
    /** @var bool */
39
    private $first = true;
40
41
    /**
42
     * @param CsvFormatInterface $csvFormat
43
     */
44 22
    public function __construct(CsvFormatInterface $csvFormat)
45
    {
46 22
        $this->csvFormat = $csvFormat;
47
48 22
        $this->buildReplacements();
49
50 22
        $this->initial = (!is_null($this->csvFormat->getBom())) ? $this->csvFormat->getBom() : '';
51
52 22
        $this->addProcessor(new DateTimeProcessor());
53 22
        $this->addProcessor(new BoolProcessor());
54 22
        $this->addProcessor(new ObjectToStringProcessor());
55 22
    }
56
57
    /**
58
     * Build replacements to perform for each entry
59
     */
60 22
    private function buildReplacements()
61
    {
62 22
        if ($this->csvFormat->getEscape()) {
63 18
            $this->escapeChars = [
64 18
                $this->csvFormat->getEscape(), // escape escape first so that it doesn't re-escape later on
65 18
                $this->csvFormat->getDelimiter(),
66 18
                "\n",
67 18
                "\r",
68 18
                "\t",
69
            ];
70 18
            if ($this->csvFormat->hasQuote() && !$this->csvFormat->useDoubleQuotes()) {
71 15
                $this->escapeChars[] = $this->csvFormat->getQuote();
72
            }
73
74 18
            $this->escapeChars = array_unique($this->escapeChars);
75
76 18
            $this->replaceChars = array_map(function ($char) {
77 18
                return $this->csvFormat->getEscape() . $char;
78 18
            }, $this->escapeChars);
79
        }
80
81 22
        if ($this->csvFormat->hasQuote() && $this->csvFormat->useDoubleQuotes()) {
82 2
            $this->escapeChars[] = $this->csvFormat->getQuote();
83 2
            $this->replaceChars[] = str_repeat($this->csvFormat->getQuote(), 2);
84
        }
85 22
    }
86
87
    /**
88
     * Gets a prefix for headers if required
89
     *
90
     * @param array $data
91
     *
92
     * @return string
93
     */
94 14
    private function getHeaderPrefix(array $data)
95
    {
96 14
        if ($this->first && $this->csvFormat->hasHeaderRow()) {
97 3
            $this->first = false;
98 3
            $postHeaderPad = $this->csvFormat->getDataStart() - $this->csvFormat->getHeaderRow();
99 3
            return $this->format(array_keys($data)) . str_repeat($this->getRowSeparator(), $postHeaderPad);
100
        }
101 14
        return '';
102
    }
103
104
    /**
105
     * @param array|Traversable $row
106
     *
107
     * @return string
108
     */
109 15
    public function format($row)
110
    {
111 15
        $data = $this->getArray($row);
112 14
        $prefix = $this->getHeaderPrefix($data);
113 14
        $data = $this->process($data);
114
115 14
        foreach ($data as &$element) {
116 14
            if (is_null($element)) {
117 4
                $element = $this->csvFormat->getNullValue();
118
            } else {
119 14
                $element = $this->csvFormat->getQuote() . $this->escape($element) . $this->csvFormat->getQuote();
120
            }
121
        }
122
123 14
        return $prefix . $this->encode(implode($this->csvFormat->getDelimiter(), $data));
124
    }
125
126
    /**
127
     * @param string $string
128
     *
129
     * @return string
130
     */
131 14
    protected function escape($string)
132
    {
133 14
        return str_replace($this->escapeChars, $this->replaceChars, $string);
134
    }
135
136
    /**
137
     * @param string $string
138
     *
139
     * @return string
140
     */
141 18
    private function encode($string)
142
    {
143 18
        return mb_convert_encoding($string, $this->csvFormat->getEncoding());
144
    }
145
146
    /**
147
     * Return an initial block if required
148
     *
149
     * @return string
150
     */
151 6
    public function getInitialBlock()
152
    {
153 6
        $linePad = $this->csvFormat->hasHeaderRow() ?
154 1
            $this->csvFormat->getHeaderRow() - 1 :
155 6
            $this->csvFormat->getDataStart() - 1;
156
157 6
        return $this->initial . str_repeat($this->getRowSeparator(), $linePad);
158
    }
159
160
    /**
161
     * Get a separator between each row
162
     *
163
     * @return string
164
     */
165 10
    public function getRowSeparator()
166
    {
167 10
        return $this->encode($this->csvFormat->getNewLine());
168
    }
169
170
    /**
171
     * Return a closing block if required
172
     *
173
     * @return string
174
     */
175 6
    public function getClosingBlock()
176
    {
177 6
        return '';
178
    }
179
}
180