note 86629 added to function.explode

From: Date: Mon, 27 Oct 2008 08:54:44 +0000
Subject: note 86629 added to function.explode
Groups: php.notes 
Request: Send a blank email to php-notes+get-145792@lists.php.net to get a copy of this message
Like tajhlande at gmail dot com, below, I needed a routine that could read CSV files without problems. The typical way of doing this (i.e. exploding each line read from the CSV file) doesn't work if some fields from the input are demarcated by quotes, and others aren't. If the separator (or delimiter, in php explode terms) occurs inside a field that is demarcated by quotes, the field will be split in two. Tajhlande's solution works great; however, as Tajhlande already admitted, it doesn't accept separators or (what I like to call) 'demarcators' that consist of multiple characters. Also, looking at Tajhlande's solution, I got the feeling that faster solutions should be possible. Indeed, according to one test I performed, the code below is twice as fast. <?php function explode_csv($sep, $string, $dem = '') { // The return value, an array of fields. $retval = array(); if(! isset($sep{0})) { // If the separator is empty then the entire record consists of one field: the input $string itself. $retval[] = $string; return $retval; } // Determine the lengths of the separator and demarcator here, to prevent (more) expensive // strlen() calls inside the while-loop, below. $sep_len = strlen($sep); $dem_len = strlen($dem); // Make sure at least one separator is found. This is a hack to make the code below a bit simpler. $string = trim($string . $sep); while(isset($string{1})) // Is $string > $sep? { // If a demarcator is given then find its position; else its position is false (not found). $dem_pos = $dem_len ? strpos($string, $dem) : false; // A separator can always be found because it was added to $string, above. $sep_pos = strpos($string, $sep); if($dem_pos === false) { // If a demarcator is not found then the field is everything up to (but not including) the separator. $start = 0; $end = $sep_pos; $next = $sep_pos + $sep_len; } else { if($sep_pos < $dem_pos) { // If a separator was found before the demarcator then the field is everything up to (but not including) the separator. $start = 0; $end = $sep_pos; $next = $sep_pos + $sep_len; } else { // The demarcator was found before a separator; find the position where to start looking for the closing demarcator. $dem_pos += $dem_lem + 1; // Look for the closing demarcator. $dem_pos2 = strpos($string, $dem . $sep, $dem_pos); if($dem_pos2 === false) { // If a closing demarcator is not found then assume that the rest of the input // (except for the separator that was added, above!) is a field. $dem_pos2 = strlen($string) - $sep_len; } $start = $dem_pos; $end = $dem_pos2 - $dem_pos; $next = $dem_pos2 + $dem_len + $sep_len; } } $retval[] = trim(substr($string, $start, $end)); // Remove the field plus the separator from the input. $string = trim(substr($string, $next)); } return $retval; } ?> This routine is intended to be a drop-in for existing explode() calls; just change the name of each call into explode_csv() and it should work --although no warranties are given! Note that the routine above assumes a relatively well-behaved input. Without actually having tested it, I'm pretty sure that this input Hello, this is "a field" will be parsed and returned as an array of two fields: "Hello", "a field" I'm using this routine on files that are produced by OpenOffice Calc or Excel and I assume/assert that these don't produce ill-behaved CSV files, so this is acceptable behavior to me. ---- Server IP: 89.248.170.61 Probable Submitter: 213.93.155.227 ---- Manual Page -- http://www.php.net/manual/en/function.explode.php Edit -- https://master.php.net/note/edit/86629 Del: integrated -- https://master.php.net/note/delete/86629/integrated Del: useless -- https://master.php.net/note/delete/86629/useless Del: bad code -- https://master.php.net/note/delete/86629/bad+code Del: spam -- https://master.php.net/note/delete/86629/spam Del: non-english -- https://master.php.net/note/delete/86629/non-english Del: in docs -- https://master.php.net/note/delete/86629/in+docs Del: other reasons-- https://master.php.net/note/delete/86629 Reject -- https://master.php.net/note/reject/86629 Search -- https://master.php.net/manage/user-notes.php

« previous php.notes (#145792) next »