| 1 | <?php |
|---|
| 2 | |
|---|
| 3 | class parseCSV { |
|---|
| 4 | |
|---|
| 5 | /* |
|---|
| 6 | |
|---|
| 7 | Class: parseCSV v0.3.2 |
|---|
| 8 | http://code.google.com/p/parsecsv-for-php/ |
|---|
| 9 | |
|---|
| 10 | |
|---|
| 11 | Fully conforms to the specifications lined out on wikipedia: |
|---|
| 12 | - http://en.wikipedia.org/wiki/Comma-separated_values |
|---|
| 13 | |
|---|
| 14 | Based on the concept of Ming Hong Ng's CsvFileParser class: |
|---|
| 15 | - http://minghong.blogspot.com/2006/07/csv-parser-for-php.html |
|---|
| 16 | |
|---|
| 17 | |
|---|
| 18 | |
|---|
| 19 | Copyright (c) 2007 Jim Myhrberg (jim@zydev.info). |
|---|
| 20 | |
|---|
| 21 | Permission is hereby granted, free of charge, to any person obtaining a copy |
|---|
| 22 | of this software and associated documentation files (the "Software"), to deal |
|---|
| 23 | in the Software without restriction, including without limitation the rights |
|---|
| 24 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
|---|
| 25 | copies of the Software, and to permit persons to whom the Software is |
|---|
| 26 | furnished to do so, subject to the following conditions: |
|---|
| 27 | |
|---|
| 28 | The above copyright notice and this permission notice shall be included in |
|---|
| 29 | all copies or substantial portions of the Software. |
|---|
| 30 | |
|---|
| 31 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
|---|
| 32 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
|---|
| 33 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
|---|
| 34 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
|---|
| 35 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
|---|
| 36 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN |
|---|
| 37 | THE SOFTWARE. |
|---|
| 38 | |
|---|
| 39 | |
|---|
| 40 | |
|---|
| 41 | Code Examples |
|---|
| 42 | ---------------- |
|---|
| 43 | # general usage |
|---|
| 44 | $csv = new parseCSV('data.csv'); |
|---|
| 45 | print_r($csv->data); |
|---|
| 46 | ---------------- |
|---|
| 47 | # tab delimited, and encoding conversion |
|---|
| 48 | $csv = new parseCSV(); |
|---|
| 49 | $csv->encoding('UTF-16', 'UTF-8'); |
|---|
| 50 | $csv->delimiter = "\t"; |
|---|
| 51 | $csv->parse('data.tsv'); |
|---|
| 52 | print_r($csv->data); |
|---|
| 53 | ---------------- |
|---|
| 54 | # auto-detect delimiter character |
|---|
| 55 | $csv = new parseCSV(); |
|---|
| 56 | $csv->auto('data.csv'); |
|---|
| 57 | print_r($csv->data); |
|---|
| 58 | ---------------- |
|---|
| 59 | # modify data in a csv file |
|---|
| 60 | $csv = new parseCSV(); |
|---|
| 61 | $csv->sort_by = 'id'; |
|---|
| 62 | $csv->parse('data.csv'); |
|---|
| 63 | # "4" is the value of the "id" column of the CSV row |
|---|
| 64 | $csv->data[4] = array('firstname' => 'John', 'lastname' => 'Doe', 'email' => 'john@doe.com'); |
|---|
| 65 | $csv->save(); |
|---|
| 66 | ---------------- |
|---|
| 67 | # add row/entry to end of CSV file |
|---|
| 68 | # - only recommended when you know the extact sctructure of the file |
|---|
| 69 | $csv = new parseCSV(); |
|---|
| 70 | $csv->save('data.csv', array('1986', 'Home', 'Nowhere', ''), true); |
|---|
| 71 | ---------------- |
|---|
| 72 | # convert 2D array to csv data and send headers |
|---|
| 73 | # to browser to treat output as a file and download it |
|---|
| 74 | $csv = new parseCSV(); |
|---|
| 75 | $csv->output (true, 'movies.csv', $array); |
|---|
| 76 | ---------------- |
|---|
| 77 | |
|---|
| 78 | |
|---|
| 79 | */ |
|---|
| 80 | |
|---|
| 81 | |
|---|
| 82 | /** |
|---|
| 83 | * Configuration |
|---|
| 84 | * - set these options with $object->var_name = 'value'; |
|---|
| 85 | */ |
|---|
| 86 | |
|---|
| 87 | # use first line/entry as field names |
|---|
| 88 | var $heading = true; |
|---|
| 89 | |
|---|
| 90 | # override field names |
|---|
| 91 | var $fields = array(); |
|---|
| 92 | |
|---|
| 93 | # sort entries by this field |
|---|
| 94 | var $sort_by = null; |
|---|
| 95 | var $sort_reverse = false; |
|---|
| 96 | |
|---|
| 97 | # delimiter (comma) and enclosure (double quote) |
|---|
| 98 | var $delimiter = ','; |
|---|
| 99 | var $enclosure = '"'; |
|---|
| 100 | |
|---|
| 101 | # basic SQL-like conditions for row matching |
|---|
| 102 | var $conditions = null; |
|---|
| 103 | |
|---|
| 104 | # number of rows to ignore from beginning of data |
|---|
| 105 | var $offset = null; |
|---|
| 106 | |
|---|
| 107 | # limits the number of returned rows to specified amount |
|---|
| 108 | var $limit = null; |
|---|
| 109 | |
|---|
| 110 | # number of rows to analyze when attempting to auto-detect delimiter |
|---|
| 111 | var $auto_depth = 15; |
|---|
| 112 | |
|---|
| 113 | # characters to ignore when attempting to auto-detect delimiter |
|---|
| 114 | var $auto_non_chars = "a-zA-Z0-9\n\r"; |
|---|
| 115 | |
|---|
| 116 | # preferred delimiter characters, only used when all filtering method |
|---|
| 117 | # returns multiple possible delimiters (happens very rarely) |
|---|
| 118 | var $auto_preferred = ",;\t.:|"; |
|---|
| 119 | |
|---|
| 120 | # character encoding options |
|---|
| 121 | var $convert_encoding = false; |
|---|
| 122 | var $input_encoding = 'ISO-8859-1'; |
|---|
| 123 | var $output_encoding = 'ISO-8859-1'; |
|---|
| 124 | |
|---|
| 125 | # used by unparse(), save(), and output() functions |
|---|
| 126 | var $linefeed = "\r\n"; |
|---|
| 127 | |
|---|
| 128 | # only used by output() function |
|---|
| 129 | var $output_delimiter = ','; |
|---|
| 130 | var $output_filename = 'data.csv'; |
|---|
| 131 | |
|---|
| 132 | |
|---|
| 133 | /** |
|---|
| 134 | * Internal variables |
|---|
| 135 | */ |
|---|
| 136 | |
|---|
| 137 | # current file |
|---|
| 138 | var $file; |
|---|
| 139 | |
|---|
| 140 | # loaded file contents |
|---|
| 141 | var $file_data; |
|---|
| 142 | |
|---|
| 143 | # array of field values in data parsed |
|---|
| 144 | var $titles = array(); |
|---|
| 145 | |
|---|
| 146 | # two dimentional array of CSV data |
|---|
| 147 | var $data = array(); |
|---|
| 148 | |
|---|
| 149 | |
|---|
| 150 | /** |
|---|
| 151 | * Constructor |
|---|
| 152 | * @param input CSV file or string |
|---|
| 153 | * @return nothing |
|---|
| 154 | */ |
|---|
| 155 | function parseCSV ($input = null, $offset = null, $limit = null, $conditions = null) { |
|---|
| 156 | if ( $offset !== null ) $this->offset = $offset; |
|---|
| 157 | if ( $limit !== null ) $this->limit = $limit; |
|---|
| 158 | if ( count($conditions) > 0 ) $this->conditions = $conditions; |
|---|
| 159 | if ( !empty($input) ) $this->parse($input); |
|---|
| 160 | } |
|---|
| 161 | |
|---|
| 162 | |
|---|
| 163 | // ============================================== |
|---|
| 164 | // ----- [ Main Functions ] --------------------- |
|---|
| 165 | // ============================================== |
|---|
| 166 | |
|---|
| 167 | /** |
|---|
| 168 | * Parse CSV file or string |
|---|
| 169 | * @param input CSV file or string |
|---|
| 170 | * @return nothing |
|---|
| 171 | */ |
|---|
| 172 | function parse ($input = null, $offset = null, $limit = null, $conditions = null) { |
|---|
| 173 | if ( !empty($input) ) { |
|---|
| 174 | if ( $offset !== null ) $this->offset = $offset; |
|---|
| 175 | if ( $limit !== null ) $this->limit = $limit; |
|---|
| 176 | if ( count($conditions) > 0 ) $this->conditions = $conditions; |
|---|
| 177 | if ( is_readable($input) ) { |
|---|
| 178 | $this->data = $this->parse_file($input); |
|---|
| 179 | } else { |
|---|
| 180 | $this->file_data = &$input; |
|---|
| 181 | $this->data = $this->parse_string(); |
|---|
| 182 | } |
|---|
| 183 | if ( $this->data === false ) return false; |
|---|
| 184 | } |
|---|
| 185 | return true; |
|---|
| 186 | } |
|---|
| 187 | |
|---|
| 188 | /** |
|---|
| 189 | * Save changes, or new file and/or data |
|---|
| 190 | * @param file file to save to |
|---|
| 191 | * @param data 2D array with data |
|---|
| 192 | * @param append append current data to end of target CSV if exists |
|---|
| 193 | * @param fields field names |
|---|
| 194 | * @return true or false |
|---|
| 195 | */ |
|---|
| 196 | function save ($file = null, $data = array(), $append = false, $fields = array()) { |
|---|
| 197 | if ( empty($file) ) $file = &$this->file; |
|---|
| 198 | $mode = ( $append ) ? 'at' : 'wt' ; |
|---|
| 199 | $is_php = ( preg_match('/\.php$/i', $file) ) ? true : false ; |
|---|
| 200 | return $this->_wfile($file, $this->unparse($data, $fields, $append, $is_php), $mode); |
|---|
| 201 | } |
|---|
| 202 | |
|---|
| 203 | /** |
|---|
| 204 | * Generate CSV based string for output |
|---|
| 205 | * @param output if true, prints headers and strings to browser |
|---|
| 206 | * @param filename filename sent to browser in headers if output is true |
|---|
| 207 | * @param data 2D array with data |
|---|
| 208 | * @param fields field names |
|---|
| 209 | * @param delimiter delimiter used to separate data |
|---|
| 210 | * @return CSV data using delimiter of choice, or default |
|---|
| 211 | */ |
|---|
| 212 | function output ($output = true, $filename = null, $data = array(), $fields = array(), $delimiter = null) { |
|---|
| 213 | if ( empty($filename) ) $filename = $this->output_filename; |
|---|
| 214 | if ( $delimiter === null ) $delimiter = $this->output_delimiter; |
|---|
| 215 | $data = $this->unparse($data, $fields, null, null, $delimiter); |
|---|
| 216 | if ( $output ) { |
|---|
| 217 | header('Content-type: application/csv'); |
|---|
| 218 | header('Content-Disposition: inline; filename="'.$filename.'"'); |
|---|
| 219 | echo $data; |
|---|
| 220 | } |
|---|
| 221 | return $data; |
|---|
| 222 | } |
|---|
| 223 | |
|---|
| 224 | /** |
|---|
| 225 | * Convert character encoding |
|---|
| 226 | * @param input input character encoding, uses default if left blank |
|---|
| 227 | * @param output output character encoding, uses default if left blank |
|---|
| 228 | * @return nothing |
|---|
| 229 | */ |
|---|
| 230 | function encoding ($input = null, $output = null) { |
|---|
| 231 | $this->convert_encoding = true; |
|---|
| 232 | if ( $input !== null ) $this->input_encoding = $input; |
|---|
| 233 | if ( $output !== null ) $this->output_encoding = $output; |
|---|
| 234 | } |
|---|
| 235 | |
|---|
| 236 | /** |
|---|
| 237 | * Auto-Detect Delimiter: Find delimiter by analyzing a specific number of |
|---|
| 238 | * rows to determine most probable delimiter character |
|---|
| 239 | * @param file local CSV file |
|---|
| 240 | * @param parse true/false parse file directly |
|---|
| 241 | * @param search_depth number of rows to analyze |
|---|
| 242 | * @param preferred preferred delimiter characters |
|---|
| 243 | * @param enclosure enclosure character, default is double quote ("). |
|---|
| 244 | * @return delimiter character |
|---|
| 245 | */ |
|---|
| 246 | function auto ($file = null, $parse = true, $search_depth = null, $preferred = null, $enclosure = null) { |
|---|
| 247 | |
|---|
| 248 | if ( $file === null ) $file = $this->file; |
|---|
| 249 | if ( empty($search_depth) ) $search_depth = $this->auto_depth; |
|---|
| 250 | if ( $enclosure === null ) $enclosure = $this->enclosure; |
|---|
| 251 | |
|---|
| 252 | if ( $preferred === null ) $preferred = $this->auto_preferred; |
|---|
| 253 | |
|---|
| 254 | if ( empty($this->file_data) ) { |
|---|
| 255 | if ( $this->_check_data($file) ) { |
|---|
| 256 | $data = &$this->file_data; |
|---|
| 257 | } else return false; |
|---|
| 258 | } else { |
|---|
| 259 | $data = &$this->file_data; |
|---|
| 260 | } |
|---|
| 261 | |
|---|
| 262 | $chars = array(); |
|---|
| 263 | $strlen = strlen($data); |
|---|
| 264 | $enclosed = false; |
|---|
| 265 | $n = 1; |
|---|
| 266 | $to_end = true; |
|---|
| 267 | |
|---|
| 268 | // walk specific depth finding posssible delimiter characters |
|---|
| 269 | for ( $i=0; $i < $strlen; $i++ ) { |
|---|
| 270 | $ch = $data{$i}; |
|---|
| 271 | $nch = ( isset($data{$i+1}) ) ? $data{$i+1} : false ; |
|---|
| 272 | $pch = ( isset($data{$i-1}) ) ? $data{$i-1} : false ; |
|---|
| 273 | |
|---|
| 274 | // open and closing quotes |
|---|
| 275 | if ( $ch == $enclosure && (!$enclosed || $nch != $enclosure) ) { |
|---|
| 276 | $enclosed = ( $enclosed ) ? false : true ; |
|---|
| 277 | |
|---|
| 278 | // inline quotes |
|---|
| 279 | } elseif ( $ch == $enclosure && $enclosed ) { |
|---|
| 280 | $i++; |
|---|
| 281 | |
|---|
| 282 | // end of row |
|---|
| 283 | } elseif ( ($ch == "\n" && $pch != "\r" || $ch == "\r") && !$enclosed ) { |
|---|
| 284 | if ( $n >= $search_depth ) { |
|---|
| 285 | $strlen = 0; |
|---|
| 286 | $to_end = false; |
|---|
| 287 | } else { |
|---|
| 288 | $n++; |
|---|
| 289 | } |
|---|
| 290 | |
|---|
| 291 | // count character |
|---|
| 292 | } elseif (!$enclosed) { |
|---|
| 293 | if ( !preg_match('/['.preg_quote($this->auto_non_chars, '/').']/i', $ch) ) { |
|---|
| 294 | if ( !isset($chars[$ch][$n]) ) { |
|---|
| 295 | $chars[$ch][$n] = 1; |
|---|
| 296 | } else { |
|---|
| 297 | $chars[$ch][$n]++; |
|---|
| 298 | } |
|---|
| 299 | } |
|---|
| 300 | } |
|---|
| 301 | } |
|---|
| 302 | |
|---|
| 303 | // filtering |
|---|
| 304 | $depth = ( $to_end ) ? $n-1 : $n ; |
|---|
| 305 | $filtered = array(); |
|---|
| 306 | foreach( $chars as $char => $value ) { |
|---|
| 307 | if ( $match = $this->_check_count($char, $value, $depth, $preferred) ) { |
|---|
| 308 | $filtered[$match] = $char; |
|---|
| 309 | } |
|---|
| 310 | } |
|---|
| 311 | |
|---|
| 312 | // capture most probable delimiter |
|---|
| 313 | ksort($filtered); |
|---|
| 314 | $delimiter = reset($filtered); |
|---|
| 315 | $this->delimiter = $delimiter; |
|---|
| 316 | |
|---|
| 317 | // parse data |
|---|
| 318 | if ( $parse ) $this->data = $this->parse_string(); |
|---|
| 319 | |
|---|
| 320 | return $delimiter; |
|---|
| 321 | |
|---|
| 322 | } |
|---|
| 323 | |
|---|
| 324 | |
|---|
| 325 | // ============================================== |
|---|
| 326 | // ----- [ Core Functions ] --------------------- |
|---|
| 327 | // ============================================== |
|---|
| 328 | |
|---|
| 329 | /** |
|---|
| 330 | * Read file to string and call parse_string() |
|---|
| 331 | * @param file local CSV file |
|---|
| 332 | * @return 2D array with CSV data, or false on failure |
|---|
| 333 | */ |
|---|
| 334 | function parse_file ($file = null) { |
|---|
| 335 | if ( $file === null ) $file = $this->file; |
|---|
| 336 | if ( empty($this->file_data) ) $this->load_data($file); |
|---|
| 337 | return ( !empty($this->file_data) ) ? $this->parse_string() : false ; |
|---|
| 338 | } |
|---|
| 339 | |
|---|
| 340 | /** |
|---|
| 341 | * Parse CSV strings to arrays |
|---|
| 342 | * @param data CSV string |
|---|
| 343 | * @return 2D array with CSV data, or false on failure |
|---|
| 344 | */ |
|---|
| 345 | function parse_string ($data = null) { |
|---|
| 346 | if ( empty($data) ) { |
|---|
| 347 | if ( $this->_check_data() ) { |
|---|
| 348 | $data = &$this->file_data; |
|---|
| 349 | } else return false; |
|---|
| 350 | } |
|---|
| 351 | |
|---|
| 352 | $rows = array(); |
|---|
| 353 | $row = array(); |
|---|
| 354 | $row_count = 0; |
|---|
| 355 | $current = ''; |
|---|
| 356 | $head = ( !empty($this->fields) ) ? $this->fields : array() ; |
|---|
| 357 | $col = 0; |
|---|
| 358 | $enclosed = false; |
|---|
| 359 | $was_enclosed = false; |
|---|
| 360 | $strlen = strlen($data); |
|---|
| 361 | |
|---|
| 362 | // --nrieb-- Adding support for escaping quotes with a backslash (\) character |
|---|
| 363 | $data = str_replace('\"', '""', $data); |
|---|
| 364 | |
|---|
| 365 | // walk through each character |
|---|
| 366 | for ( $i=0; $i < $strlen; $i++ ) { |
|---|
| 367 | $ch = $data{$i}; |
|---|
| 368 | $nch = ( isset($data{$i+1}) ) ? $data{$i+1} : false ; |
|---|
| 369 | $pch = ( isset($data{$i-1}) ) ? $data{$i-1} : false ; |
|---|
| 370 | |
|---|
| 371 | // open and closing quotes |
|---|
| 372 | if ( $ch == $this->enclosure && (!$enclosed || $nch != $this->enclosure) ) { |
|---|
| 373 | $enclosed = ( $enclosed ) ? false : true ; |
|---|
| 374 | if ( $enclosed ) $was_enclosed = true; |
|---|
| 375 | |
|---|
| 376 | // inline quotes |
|---|
| 377 | } elseif ( $ch == $this->enclosure && $enclosed ) { |
|---|
| 378 | $current .= $ch; |
|---|
| 379 | $i++; |
|---|
| 380 | |
|---|
| 381 | // end of field/row |
|---|
| 382 | } elseif ( ($ch == $this->delimiter || ($ch == "\n" && $pch != "\r") || $ch == "\r") && !$enclosed ) { |
|---|
| 383 | if ( !$was_enclosed ) $current = trim($current); |
|---|
| 384 | $key = ( !empty($head[$col]) ) ? $head[$col] : $col ; |
|---|
| 385 | $row[$key] = $current; |
|---|
| 386 | $current = ''; |
|---|
| 387 | $col++; |
|---|
| 388 | |
|---|
| 389 | // end of row |
|---|
| 390 | if ( $ch == "\n" || $ch == "\r" ) { |
|---|
| 391 | if ( $this->_validate_offset($row_count) && $this->_validate_row_conditions($row, $this->conditions) ) { |
|---|
| 392 | if ( $this->heading && empty($head) ) { |
|---|
| 393 | $head = $row; |
|---|
| 394 | } elseif ( empty($this->fields) || (!empty($this->fields) && (($this->heading && $row_count > 0) || !$this->heading)) ) { |
|---|
| 395 | if ( !empty($this->sort_by) && !empty($row[$this->sort_by]) ) { |
|---|
| 396 | if ( isset($rows[$row[$this->sort_by]]) ) { |
|---|
| 397 | $rows[$row[$this->sort_by].'_0'] = &$rows[$row[$this->sort_by]]; |
|---|
| 398 | unset($rows[$row[$this->sort_by]]); |
|---|
| 399 | for ( $sn=1; isset($rows[$row[$this->sort_by].'_'.$sn]); $sn++ ) {} |
|---|
| 400 | $rows[$row[$this->sort_by].'_'.$sn] = $row; |
|---|
| 401 | } else $rows[$row[$this->sort_by]] = $row; |
|---|
| 402 | } else $rows[] = $row; |
|---|
| 403 | } |
|---|
| 404 | } |
|---|
| 405 | $row = array(); |
|---|
| 406 | $col = 0; |
|---|
| 407 | $row_count++; |
|---|
| 408 | if ( $this->sort_by === null && $this->limit !== null && count($rows) == $this->limit ) { |
|---|
| 409 | $i = $strlen; |
|---|
| 410 | } |
|---|
| 411 | } |
|---|
| 412 | |
|---|
| 413 | // append character to current field |
|---|
| 414 | } else { |
|---|
| 415 | $current .= $ch; |
|---|
| 416 | } |
|---|
| 417 | } |
|---|
| 418 | $this->titles = $head; |
|---|
| 419 | if ( !empty($this->sort_by) ) { |
|---|
| 420 | ( $this->sort_reverse ) ? krsort($rows) : ksort($rows) ; |
|---|
| 421 | if ( $this->offset !== null || $this->limit !== null ) { |
|---|
| 422 | $rows = array_slice($rows, ($this->offset === null ? 0 : $this->offset) , $this->limit, true); |
|---|
| 423 | } |
|---|
| 424 | } |
|---|
| 425 | return $rows; |
|---|
| 426 | } |
|---|
| 427 | |
|---|
| 428 | /** |
|---|
| 429 | * Create CSV data from array |
|---|
| 430 | * @param data 2D array with data |
|---|
| 431 | * @param fields field names |
|---|
| 432 | * @param append if true, field names will not be output |
|---|
| 433 | * @param is_php if a php die() call should be put on the first |
|---|
| 434 | * line of the file, this is later ignored when read. |
|---|
| 435 | * @param delimiter field delimiter to use |
|---|
| 436 | * @return CSV data (text string) |
|---|
| 437 | */ |
|---|
| 438 | function unparse ( $data = array(), $fields = array(), $append = false , $is_php = false, $delimiter = null) { |
|---|
| 439 | if ( !is_array($data) || empty($data) ) $data = &$this->data; |
|---|
| 440 | if ( !is_array($fields) || empty($fields) ) $fields = &$this->titles; |
|---|
| 441 | if ( $delimiter === null ) $delimiter = $this->delimiter; |
|---|
| 442 | |
|---|
| 443 | $string = ( $is_php ) ? "<?php header('Status: 403'); die(' '); ?>".$this->linefeed : '' ; |
|---|
| 444 | $entry = array(); |
|---|
| 445 | |
|---|
| 446 | // create heading |
|---|
| 447 | if ( $this->heading && !$append ) { |
|---|
| 448 | foreach( $fields as $key => $value ) { |
|---|
| 449 | $entry[] = $this->_enclose_value($value); |
|---|
| 450 | } |
|---|
| 451 | $string .= implode($delimiter, $entry).$this->linefeed; |
|---|
| 452 | $entry = array(); |
|---|
| 453 | } |
|---|
| 454 | |
|---|
| 455 | // create data |
|---|
| 456 | foreach( $data as $key => $row ) { |
|---|
| 457 | foreach( $row as $field => $value ) { |
|---|
| 458 | $entry[] = $this->_enclose_value($value); |
|---|
| 459 | } |
|---|
| 460 | $string .= implode($delimiter, $entry).$this->linefeed; |
|---|
| 461 | $entry = array(); |
|---|
| 462 | } |
|---|
| 463 | |
|---|
| 464 | return $string; |
|---|
| 465 | } |
|---|
| 466 | |
|---|
| 467 | /** |
|---|
| 468 | * Load local file or string |
|---|
| 469 | * @param input local CSV file |
|---|
| 470 | * @return true or false |
|---|
| 471 | */ |
|---|
| 472 | function load_data ($input = null) { |
|---|
| 473 | $data = null; |
|---|
| 474 | $file = null; |
|---|
| 475 | if ( $input === null ) { |
|---|
| 476 | $file = $this->file; |
|---|
| 477 | } elseif ( file_exists($input) ) { |
|---|
| 478 | $file = $input; |
|---|
| 479 | } else { |
|---|
| 480 | $data = $input; |
|---|
| 481 | } |
|---|
| 482 | if ( !empty($data) || $data = $this->_rfile($file) ) { |
|---|
| 483 | if ( $this->file != $file ) $this->file = $file; |
|---|
| 484 | if ( preg_match('/\.php$/i', $file) && preg_match('/<\?.*?\?>(.*)/ims', $data, $strip) ) { |
|---|
| 485 | $data = ltrim($strip[1]); |
|---|
| 486 | } |
|---|
| 487 | if ( $this->convert_encoding ) $data = iconv($this->input_encoding, $this->output_encoding, $data); |
|---|
| 488 | if ( substr($data, -1) != "\n" ) $data .= "\n"; |
|---|
| 489 | $this->file_data = &$data; |
|---|
| 490 | return true; |
|---|
| 491 | } |
|---|
| 492 | return false; |
|---|
| 493 | } |
|---|
| 494 | |
|---|
| 495 | |
|---|
| 496 | // ============================================== |
|---|
| 497 | // ----- [ Internal Functions ] ----------------- |
|---|
| 498 | // ============================================== |
|---|
| 499 | |
|---|
| 500 | /** |
|---|
| 501 | * Validate a row against specified conditions |
|---|
| 502 | * @param row array with values from a row |
|---|
| 503 | * @param conditions specified conditions that the row must match |
|---|
| 504 | * @return true of false |
|---|
| 505 | */ |
|---|
| 506 | function _validate_row_conditions ($row = array(), $conditions = null) { |
|---|
| 507 | if ( !empty($row) ) { |
|---|
| 508 | if ( !empty($conditions) ) { |
|---|
| 509 | $conditions = (strpos($conditions, ' OR ') !== false) ? explode(' OR ', $conditions) : array($conditions) ; |
|---|
| 510 | $or = ''; |
|---|
| 511 | foreach( $conditions as $key => $value ) { |
|---|
| 512 | if ( strpos($value, ' AND ') !== false ) { |
|---|
| 513 | $value = explode(' AND ', $value); |
|---|
| 514 | $and = ''; |
|---|
| 515 | foreach( $value as $k => $v ) { |
|---|
| 516 | $and .= $this->_validate_row_condition($row, $v); |
|---|
| 517 | } |
|---|
| 518 | $or .= (strpos($and, '0') !== false) ? '0' : '1' ; |
|---|
| 519 | } else { |
|---|
| 520 | $or .= $this->_validate_row_condition($row, $value); |
|---|
| 521 | } |
|---|
| 522 | } |
|---|
| 523 | return (strpos($or, '1') !== false) ? true : false ; |
|---|
| 524 | } |
|---|
| 525 | return true; |
|---|
| 526 | } |
|---|
| 527 | return false; |
|---|
| 528 | } |
|---|
| 529 | |
|---|
| 530 | /** |
|---|
| 531 | * Validate a row against a single condition |
|---|
| 532 | * @param row array with values from a row |
|---|
| 533 | * @param condition specified condition that the row must match |
|---|
| 534 | * @return true of false |
|---|
| 535 | */ |
|---|
| 536 | function _validate_row_condition ($row, $condition) { |
|---|
| 537 | $operators = array( |
|---|
| 538 | '=', 'equals', 'is', |
|---|
| 539 | '!=', 'is not', |
|---|
| 540 | '<', 'is less than', |
|---|
| 541 | '>', 'is greater than', |
|---|
| 542 | '<=', 'is less than or equals', |
|---|
| 543 | '>=', 'is greater than or equals', |
|---|
| 544 | 'contains', |
|---|
| 545 | 'does not contain', |
|---|
| 546 | ); |
|---|
| 547 | $operators_regex = array(); |
|---|
| 548 | foreach( $operators as $value ) { |
|---|
| 549 | $operators_regex[] = preg_quote($value, '/'); |
|---|
| 550 | } |
|---|
| 551 | $operators_regex = implode('|', $operators_regex); |
|---|
| 552 | if ( preg_match('/^(.+) ('.$operators_regex.') (.+)$/i', trim($condition), $capture) ) { |
|---|
| 553 | $field = $capture[1]; |
|---|
| 554 | $op = $capture[2]; |
|---|
| 555 | $value = $capture[3]; |
|---|
| 556 | if ( preg_match('/^([\'\"]{1})(.*)([\'\"]{1})$/i', $value, $capture) ) { |
|---|
| 557 | if ( $capture[1] == $capture[3] ) { |
|---|
| 558 | $value = $capture[2]; |
|---|
| 559 | $value = str_replace("\\n", "\n", $value); |
|---|
| 560 | $value = str_replace("\\r", "\r", $value); |
|---|
| 561 | $value = str_replace("\\t", "\t", $value); |
|---|
| 562 | $value = stripslashes($value); |
|---|
| 563 | } |
|---|
| 564 | } |
|---|
| 565 | if ( array_key_exists($field, $row) ) { |
|---|
| 566 | if ( ($op == '=' || $op == 'equals' || $op == 'is') && $row[$field] == $value ) { |
|---|
| 567 | return '1'; |
|---|
| 568 | } elseif ( ($op == '!=' || $op == 'is not') && $row[$field] != $value ) { |
|---|
| 569 | return '1'; |
|---|
| 570 | } elseif ( ($op == '<' || $op == 'is less than' ) && $row[$field] < $value ) { |
|---|
| 571 | return '1'; |
|---|
| 572 | } elseif ( ($op == '>' || $op == 'is greater than') && $row[$field] > $value ) { |
|---|
| 573 | return '1'; |
|---|
| 574 | } elseif ( ($op == '<=' || $op == 'is less than or equals' ) && $row[$field] <= $value ) { |
|---|
| 575 | return '1'; |
|---|
| 576 | } elseif ( ($op == '>=' || $op == 'is greater than or equals') && $row[$field] >= $value ) { |
|---|
| 577 | return '1'; |
|---|
| 578 | } elseif ( $op == 'contains' && preg_match('/'.preg_quote($value, '/').'/i', $row[$field]) ) { |
|---|
| 579 | return '1'; |
|---|
| 580 | } elseif ( $op == 'does not contain' && !preg_match('/'.preg_quote($value, '/').'/i', $row[$field]) ) { |
|---|
| 581 | return '1'; |
|---|
| 582 | } else { |
|---|
| 583 | return '0'; |
|---|
| 584 | } |
|---|
| 585 | } |
|---|
| 586 | } |
|---|
| 587 | return '1'; |
|---|
| 588 | } |
|---|
| 589 | |
|---|
| 590 | /** |
|---|
| 591 | * Validates if the row is within the offset or not if sorting is disabled |
|---|
| 592 | * @param current_row the current row number being processed |
|---|
| 593 | * @return true of false |
|---|
| 594 | */ |
|---|
| 595 | function _validate_offset ($current_row) { |
|---|
| 596 | if ( $this->sort_by === null && $this->offset !== null && $current_row < $this->offset ) return false; |
|---|
| 597 | return true; |
|---|
| 598 | } |
|---|
| 599 | |
|---|
| 600 | /** |
|---|
| 601 | * Enclose values if needed |
|---|
| 602 | * - only used by unparse() |
|---|
| 603 | * @param value string to process |
|---|
| 604 | * @return Processed value |
|---|
| 605 | */ |
|---|
| 606 | function _enclose_value ($value = null) { |
|---|
| 607 | if ( $value !== null && $value != '' ) { |
|---|
| 608 | $delimiter = preg_quote($this->delimiter, '/'); |
|---|
| 609 | $enclosure = preg_quote($this->enclosure, '/'); |
|---|
| 610 | if ( preg_match("/".$delimiter."|".$enclosure."|\n|\r/i", $value) || ($value{0} == ' ' || substr($value, -1) == ' ') ) { |
|---|
| 611 | $value = str_replace($this->enclosure, $this->enclosure.$this->enclosure, $value); |
|---|
| 612 | $value = $this->enclosure.$value.$this->enclosure; |
|---|
| 613 | } |
|---|
| 614 | } |
|---|
| 615 | return $value; |
|---|
| 616 | } |
|---|
| 617 | |
|---|
| 618 | /** |
|---|
| 619 | * Check file data |
|---|
| 620 | * @param file local filename |
|---|
| 621 | * @return true or false |
|---|
| 622 | */ |
|---|
| 623 | function _check_data ($file = null) { |
|---|
| 624 | if ( empty($this->file_data) ) { |
|---|
| 625 | if ( $file === null ) $file = $this->file; |
|---|
| 626 | return $this->load_data($file); |
|---|
| 627 | } |
|---|
| 628 | return true; |
|---|
| 629 | } |
|---|
| 630 | |
|---|
| 631 | |
|---|
| 632 | /** |
|---|
| 633 | * Check if passed info might be delimiter |
|---|
| 634 | * - only used by find_delimiter() |
|---|
| 635 | * @return special string used for delimiter selection, or false |
|---|
| 636 | */ |
|---|
| 637 | function _check_count ($char, $array, $depth, $preferred) { |
|---|
| 638 | if ( $depth == count($array) ) { |
|---|
| 639 | $first = null; |
|---|
| 640 | $equal = null; |
|---|
| 641 | $almost = false; |
|---|
| 642 | foreach( $array as $key => $value ) { |
|---|
| 643 | if ( $first == null ) { |
|---|
| 644 | $first = $value; |
|---|
| 645 | } elseif ( $value == $first && $equal !== false) { |
|---|
| 646 | $equal = true; |
|---|
| 647 | } elseif ( $value == $first+1 && $equal !== false ) { |
|---|
| 648 | $equal = true; |
|---|
| 649 | $almost = true; |
|---|
| 650 | } else { |
|---|
| 651 | $equal = false; |
|---|
| 652 | } |
|---|
| 653 | } |
|---|
| 654 | if ( $equal ) { |
|---|
| 655 | $match = ( $almost ) ? 2 : 1 ; |
|---|
| 656 | $pref = strpos($preferred, $char); |
|---|
| 657 | $pref = ( $pref !== false ) ? str_pad($pref, 3, '0', STR_PAD_LEFT) : '999' ; |
|---|
| 658 | return $pref.$match.'.'.(99999 - str_pad($first, 5, '0', STR_PAD_LEFT)); |
|---|
| 659 | } else return false; |
|---|
| 660 | } |
|---|
| 661 | } |
|---|
| 662 | |
|---|
| 663 | /** |
|---|
| 664 | * Read local file |
|---|
| 665 | * @param file local filename |
|---|
| 666 | * @return Data from file, or false on failure |
|---|
| 667 | */ |
|---|
| 668 | function _rfile ($file = null) { |
|---|
| 669 | if ( is_readable($file) ) { |
|---|
| 670 | if ( !($fh = fopen($file, 'r')) ) return false; |
|---|
| 671 | $data = fread($fh, filesize($file)); |
|---|
| 672 | fclose($fh); |
|---|
| 673 | return $data; |
|---|
| 674 | } |
|---|
| 675 | return false; |
|---|
| 676 | } |
|---|
| 677 | |
|---|
| 678 | /** |
|---|
| 679 | * Write to local file |
|---|
| 680 | * @param file local filename |
|---|
| 681 | * @param string data to write to file |
|---|
| 682 | * @param mode fopen() mode |
|---|
| 683 | * @param lock flock() mode |
|---|
| 684 | * @return true or false |
|---|
| 685 | */ |
|---|
| 686 | function _wfile ($file, $string = '', $mode = 'wb', $lock = 2) { |
|---|
| 687 | if ( $fp = fopen($file, $mode) ) { |
|---|
| 688 | flock($fp, $lock); |
|---|
| 689 | $re = fwrite($fp, $string); |
|---|
| 690 | $re2 = fclose($fp); |
|---|
| 691 | if ( $re != false && $re2 != false ) return true; |
|---|
| 692 | } |
|---|
| 693 | return false; |
|---|
| 694 | } |
|---|
| 695 | |
|---|
| 696 | } |
|---|
| 697 | |
|---|
| 698 | ?> |
|---|