1 | <?php |
||
37 | class Writer |
||
38 | { |
||
39 | /** |
||
40 | * @var \CSVelte\Flavor |
||
41 | */ |
||
42 | protected $flavor; |
||
43 | |||
44 | /** |
||
45 | * @var \CSVelte\Contract\Writable |
||
46 | */ |
||
47 | protected $output; |
||
48 | |||
49 | /** |
||
50 | * @var \Iterator |
||
51 | */ |
||
52 | protected $headers; |
||
53 | |||
54 | /** |
||
55 | * @var int lines of data written so far (not including header) |
||
56 | */ |
||
57 | protected $written = 0; |
||
58 | |||
59 | /** |
||
60 | * Class Constructor |
||
61 | * |
||
62 | * @param \CSVelte\Contract\Writable $output An output source to write to |
||
63 | * @param \CSVelte\Flavor|array $flavor A flavor or set of formatting params |
||
64 | * @return void |
||
|
|||
65 | * @access public |
||
66 | */ |
||
67 | 17 | public function __construct(Writable $output, $flavor = null) |
|
68 | { |
||
69 | 17 | if (!($flavor instanceof Flavor)) $flavor = new Flavor($flavor); |
|
70 | 17 | $this->flavor = $flavor; |
|
71 | 17 | $this->output = $output; |
|
72 | 17 | } |
|
73 | |||
74 | /** |
||
75 | * Get the CSV flavor (or dialect) for this writer |
||
76 | * |
||
77 | * @param void |
||
78 | * @return \CSVelte\Flavor |
||
79 | * @access public |
||
80 | */ |
||
81 | 16 | public function getFlavor() |
|
82 | { |
||
83 | 16 | return $this->flavor; |
|
84 | } |
||
85 | |||
86 | /** |
||
87 | * Sets the header row |
||
88 | * If any data has been written to the output, it is too late to write the |
||
89 | * header row and an exception will be thrown. Later implementations will |
||
90 | * likely buffer the output so that this may be called after writeRows() |
||
91 | * |
||
92 | * @param \Iterator|array A list of header values |
||
93 | * @return boolean |
||
94 | * @throws \CSVelte\Exception\WriterException |
||
95 | */ |
||
96 | 2 | public function setHeaderRow($headers) |
|
104 | |||
105 | /** |
||
106 | * Write a single row |
||
107 | * |
||
108 | * @param \Iterator|array $row The row to write to source |
||
109 | * @return int The number or bytes written |
||
110 | */ |
||
111 | 14 | public function writeRow($row) |
|
112 | { |
||
113 | 14 | $eol = $this->getFlavor()->lineTerminator; |
|
114 | 14 | $delim = $this->getFlavor()->delimiter; |
|
115 | 14 | if (!$this->written && $this->headers) { |
|
116 | 1 | $headerRow = new HeaderRow((array) $this->headers); |
|
117 | 1 | $this->writeHeaderRow($headerRow); |
|
118 | 1 | } |
|
119 | 14 | if (is_array($row)) $row = new ArrayIterator($row); |
|
120 | 14 | $row = $this->prepareRow($row); |
|
121 | 14 | if ($count = $this->output->writeLine($row->join($delim), $eol)) { |
|
122 | 14 | $this->written++; |
|
123 | 14 | return $count; |
|
124 | } |
||
125 | } |
||
126 | |||
127 | 3 | protected function writeHeaderRow(HeaderRow $row) |
|
135 | |||
136 | /** |
||
137 | * Write multiple rows |
||
138 | * |
||
139 | * @param \Iterable|array $rows List of \Iterable|array |
||
140 | * @return int number of lines written |
||
141 | * @access public |
||
142 | */ |
||
143 | 11 | public function writeRows($rows) |
|
144 | { |
||
145 | 11 | if (is_array($rows)) $rows = new ArrayIterator($rows); |
|
146 | 11 | if (!($rows instanceof Iterator)) { |
|
147 | 1 | throw new InvalidArgumentException('First argument for ' . __METHOD__ . ' must be iterable'); |
|
148 | } |
||
149 | 10 | $written = 0; |
|
150 | 10 | if ($rows instanceof Reader) { |
|
151 | 2 | $this->writeHeaderRow($rows->header()); |
|
152 | 2 | } |
|
153 | 10 | foreach ($rows as $row) { |
|
154 | 10 | if ($this->writeRow($row)) $written++; |
|
155 | 10 | } |
|
156 | 10 | return $written; |
|
157 | } |
||
158 | |||
159 | /** |
||
160 | * Prepare a row of data to be written |
||
161 | * This means taking an array of data, and converting it to a Row object |
||
162 | * |
||
163 | * @param \Iterator|array of data items |
||
164 | * @return CSVelte\Table\AbstractRow |
||
165 | * @access protected |
||
166 | */ |
||
167 | 14 | protected function prepareRow(Iterator $row) |
|
168 | { |
||
169 | 14 | $items = array(); |
|
170 | 14 | foreach ($row as $data) { |
|
171 | 14 | $items []= $this->prepareData($data); |
|
172 | 14 | } |
|
173 | 14 | $row = new Row($items); |
|
174 | 14 | return $row; |
|
175 | } |
||
176 | |||
177 | /** |
||
178 | * Prepare a cell of data to be written (convert to Data object) |
||
179 | * |
||
180 | * @param string $data A string containing cell data |
||
181 | * @return string quoted string data |
||
182 | * @access protected |
||
183 | */ |
||
184 | 14 | protected function prepareData($data) |
|
191 | |||
192 | 14 | protected function quoteString($str) |
|
193 | { |
||
194 | 14 | $flvr = $this->getFlavor(); |
|
195 | // Normally I would make this a method on the class, but I don't intend |
||
196 | // to use it for very long, in fact, once I finish writing the Data class |
||
197 | // it is gonezo! |
||
198 | 14 | $hasSpecialChars = function($s) use ($flvr) { |
|
199 | 11 | $specialChars = preg_quote($flvr->lineTerminator . $flvr->quoteChar . $flvr->delimiter); |
|
200 | 11 | $pattern = "/[{$specialChars}]/m"; |
|
201 | 11 | return preg_match($pattern, $s); |
|
202 | 14 | }; |
|
203 | 14 | switch($flvr->quoteStyle) { |
|
204 | 14 | case Flavor::QUOTE_ALL: |
|
205 | 1 | $doQuote = true; |
|
206 | 1 | break; |
|
207 | 13 | case Flavor::QUOTE_NONNUMERIC: |
|
208 | 1 | $doQuote = !is_numeric($str); |
|
209 | 1 | break; |
|
210 | 12 | case Flavor::QUOTE_MINIMAL: |
|
211 | 11 | $doQuote = $hasSpecialChars($str); |
|
212 | 11 | break; |
|
213 | 1 | case Flavor::QUOTE_NONE: |
|
214 | 1 | default: |
|
215 | // @todo I think that if a cell is not quoted, newlines and delimiters should still be escaped by the escapeChar... no? |
||
216 | 1 | $doQuote = false; |
|
217 | 1 | break; |
|
218 | 14 | } |
|
219 | 14 | $quoteChar = ($doQuote) ? $flvr->quoteChar : ""; |
|
220 | 14 | return sprintf("%s%s%s", |
|
221 | 14 | $quoteChar, |
|
222 | 14 | $this->escapeString($str, $doQuote), |
|
223 | $quoteChar |
||
224 | 14 | ); |
|
225 | } |
||
226 | |||
227 | 14 | protected function escapeString($str, $isQuoted = true) |
|
235 | } |
||
236 |
Adding a
@return
annotation to a constructor is not recommended, since a constructor does not have a meaningful return value.Please refer to the PHP core documentation on constructors.