1<?php
2/**
3 * Comma Seperated Value (CSV) Data Source Driver
4 *
5 * PHP versions 4 and 5
6 *
7 * LICENSE:
8 *
9 * Copyright (c) 1997-2007, Andrew Nagy <asnagy@webitecture.org>,
10 *                          Olivier Guilyardi <olivier@samalyse.com>,
11 *                          Mark Wiesemann <wiesemann@php.net>
12 * All rights reserved.
13 *
14 * Redistribution and use in source and binary forms, with or without
15 * modification, are permitted provided that the following conditions
16 * are met:
17 *
18 *    * Redistributions of source code must retain the above copyright
19 *      notice, this list of conditions and the following disclaimer.
20 *    * Redistributions in binary form must reproduce the above copyright
21 *      notice, this list of conditions and the following disclaimer in the
22 *      documentation and/or other materials provided with the distribution.
23 *    * The names of the authors may not be used to endorse or promote products
24 *      derived from this software without specific prior written permission.
25 *
26 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
27 * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
28 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
29 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
30 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
31 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
32 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
33 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
34 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
35 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
36 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
37 *
38 * CSV file id: $Id: CSV.php,v 1.33 2007/05/14 13:12:44 olivierg Exp $
39 *
40 * @version  $Revision: 1.33 $
41 * @package  Structures_DataGrid_DataSource_CSV
42 * @category Structures
43 * @license  http://opensource.org/licenses/bsd-license.php New BSD License
44 */
45
46require_once 'Structures/DataGrid/DataSource/Array.php';
47
48/**
49 * Comma Seperated Value (CSV) Data Source Driver
50 *
51 * This class is a data source driver for a CSV File.  It will also support any
52 * other delimiter.
53 *
54 * SUPPORTED OPTIONS:
55 *
56 * - delimiter:  (string)  Field delimiter
57 * - enclosure:  (string)  Field enclosure
58 * - header:     (bool)    Whether the CSV file (or string) contains a header row
59 *
60 * @version  $Revision: 1.33 $
61 * @author   Andrew Nagy <asnagy@webitecture.org>
62 * @author   Olivier Guilyardi <olivier@samalyse.com>
63 * @author   Mark Wiesemann <wiesemann@php.net>
64 * @access   public
65 * @package  Structures_DataGrid_DataSource_CSV
66 * @category Structures
67 */
68class Structures_DataGrid_DataSource_CSV extends
69    Structures_DataGrid_DataSource_Array
70{
71    function Structures_DataGrid_DataSource_CSV()
72    {
73        parent::Structures_DataGrid_DataSource_Array();
74        $this->_addDefaultOptions(array('delimiter' => ',',
75                                        'enclosure' => '"',
76                                        'header'    => false));
77    }
78
79    /**
80     * Bind
81     *
82     * @param   mixed $csv      Can be either the path to the CSV file or a
83     *                          string containing the CSV data
84     * @access  public
85     * @return  mixed           True on success, PEAR_Error on failure
86     */
87    function bind($csv, $options = array())
88    {
89        if ($options) {
90            $this->setOptions($options);
91        }
92
93        if (strlen($csv) < 256 && @is_file($csv)) {
94            // TODO: do not read the whole file at once
95            $fp = fopen($csv, 'rb');
96            if (!$fp) {
97                return PEAR::raiseError('Could not read file');
98            }
99            clearstatcache();
100            $length = filesize($csv);
101        } else {
102            if (!Structures_DataGrid_DataSource_CSV_Memory::initialize(true)) {
103                if (!stream_wrapper_register(
104                        'structures-datagrid-datasource-csv-memory',
105                        'Structures_DataGrid_DataSource_CSV_Memory')) {
106                    return PEAR::raiseError('Could not register stream wrapper');
107                }
108                Structures_DataGrid_DataSource_CSV_Memory::initialize();
109            }
110            $fp = fopen('structures-datagrid-datasource-csv-memory://', 'r+');
111            if (!$fp) {
112                return PEAR::raiseError('Could not read from stream');
113            }
114            fwrite($fp, $csv);
115            rewind($fp);
116            $length = strlen($csv);
117        }
118
119        // if the options say that there is a header row, use the contents of it
120        // as the column names
121        if ($this->_options['header']) {
122            $keys = fgetcsv($fp, $length, $this->_options['delimiter'],
123                            $this->_options['enclosure']);
124        } else {
125            $keys = null;
126        }
127
128        // store every field (column name) that is actually used
129        $fields = $keys;
130
131        // helper variable for the case that we have a file without a header row
132        $maxkeys = 0;
133
134        while ($row = fgetcsv($fp, $length, $this->_options['delimiter'],
135                              $this->_options['enclosure'])) {
136            if (empty($keys)) {
137                $this->_ar[] = $row;
138                $maxkeys = max($maxkeys, count($row));
139            } else {
140                $rowAssoc = array();
141                foreach ($row as $index => $val) {
142                    if (!empty($keys[$index])) {
143                        $rowAssoc[$keys[$index]] = $val;
144                    } else {
145                        // there are more fields than we have column names
146                        // from the header of the CSV file => we need to use
147                        // the numeric index.
148                        if (!in_array($index, $fields, true)) {
149                            $fields[] = $index;
150                        }
151                        $rowAssoc[$index] = $val;
152                    }
153                }
154                $this->_ar[] = $rowAssoc;
155            }
156        }
157
158        // set field names if they were not set as an option
159        if (!$this->_options['fields']) {
160            if (empty($keys)) {
161                $this->_options['fields'] = range(0, $maxkeys - 1);
162            } else {
163                $this->_options['fields'] = $fields;
164            }
165        }
166
167        return true;
168    }
169
170}
171
172/**
173 * Stream wrapper for CSV DataSource driver
174 *
175 * This class is a stream wrapper for CSV data.
176 *
177 * @version  $Revision: 1.33 $
178 * @author   Mark Wiesemann <wiesemann@php.net>
179 * @access   public
180 * @package  Structures_DataGrid_DataSource_CSV
181 * @category Structures
182 */
183class Structures_DataGrid_DataSource_CSV_Memory
184{
185    /**
186     * The current position in the stream
187     *
188     * @var integer
189     * @access private
190     */
191    var $_position;
192
193    /**
194     * A string holding the stream data
195     *
196     * @var string
197     * @access private
198     */
199    var $_varname;
200
201    /**
202     * This method is called immediately after the stream object is created.
203     *
204     * @param string    $path           Path (not used)
205     * @param string    $mode           Mode (fopen(), not used)
206     * @param integer   $options        Options (not used)
207     * @param string    $opened_path    The opened path (not used)
208     * @return boolean                  true on success, false on error
209     */
210    function stream_open($path, $mode, $options, &$opened_path)
211    {
212        $this->_varname = '';
213        $this->_position = 0;
214        return true;
215    }
216
217    /**
218     * This method is called in response to fread() and fgets() calls on the
219     * stream.
220     *
221     * @param integer   $count          The number of bytes that should be read
222     * @return string                   The data that was read
223     */
224    function stream_read($count)
225    {
226        $ret = substr($this->_varname, $this->_position, $count);
227        $this->_position += strlen($ret);
228        return $ret;
229    }
230
231    /**
232     * This method is called in response to fwrite() calls on the stream.
233     *
234     * @param integer   $data           The data string that should be stored
235     * @return string                   The number of bytes that was written
236     */
237    function stream_write($data)
238    {
239        $left = substr($this->_varname, 0, $this->_position);
240        $right = substr($this->_varname, $this->_position + strlen($data));
241        $this->_varname = $left . $data . $right;
242        $this->_position += strlen($data);
243        return strlen($data);
244    }
245
246    /**
247     * This method is called in response to feof() calls on the stream.
248     *
249     * @return boolean                  Whether the read/write position is at
250     *                                  the end of the stream
251     */
252    function stream_eof()
253    {
254        return $this->_position >= strlen($this->_varname);
255    }
256
257    /**
258     * This method is called in response to ftell() calls on the stream.
259     *
260     * @return integer                  The current read/write position of the
261     *                                  stream
262     */
263    function stream_tell()
264    {
265        return $this->_position;
266    }
267
268    /**
269     * This method is called in response to fseek() calls on the stream.
270     *
271     * @param integer $offset           Offset of the new position, added to the
272     *                                  $whence position
273     * @param integer $whence           Start position; one of: SEEK_SET,
274     *                                  SEEK_CUR, SEEK_END
275     * @return boolean                  true if position was changed, false if
276     *                                  position could not be changed
277     */
278    function stream_seek($offset, $whence)
279    {
280        switch ($whence) {
281            case SEEK_SET:
282                if ($offset < strlen($this->_varname) && $offset >= 0) {
283                     $this->_position = $offset;
284                     return true;
285                } else {
286                     return false;
287                }
288                break;
289            case SEEK_CUR:
290                if ($offset >= 0) {
291                     $this->_position += $offset;
292                     return true;
293                } else {
294                     return false;
295                }
296                break;
297            case SEEK_END:
298                if (strlen($this->_varname) + $offset >= 0) {
299                     $this->_position = strlen($this->_varname) + $offset;
300                     return true;
301                } else {
302                     return false;
303                }
304                break;
305            default:
306                return false;
307        }
308    }
309
310    /**
311     * PHP 4 workaround to indicate whether the class was already initialized
312     * or not.
313     *
314     * @param boolean $checkOnly        Whether the status should only be
315     *                                  checked.
316     * @return mixed                    void or boolean
317     */
318    function initialize($checkOnly = false) {
319        static $initialized = false;
320        if ($checkOnly) {
321            return $initialized;
322        }
323        $initialized = true;
324    }
325}
326
327/* vim: set expandtab tabstop=4 shiftwidth=4: */
328?>
329