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