Properly parsing CSV in PHP

Written on by
.
Filed under

A snippet for future reference, if I ever need to parse a CSV string again. The csv_parse function takes a CSV-formatted string and returns an array (rows) of arrays (columns). The csv_parse_assoc function does the same but converts the header row of the CSV string to array keys.

Usage

$csv_string = file_get_contents('data.csv');
$csv_array = csv_parse_assoc($csv_string);

Code Listing

<?php

function csv_parse_assoc(&$string, $separator = ',', $enclosure = '"', $linebreak = "\n") {
	$array = csv_parse($string, $separator, $enclosure, $linebreak);
	$keys = array_shift($array);
	array_walk($array, function(&$a) use ($keys) {
		$a = array_combine($keys, $a);
	});

	return $array;
}

function csv_parse(&$string, $separator = ',', $enclosure = '"', $linebreak = "\n") {
	$string = preg_replace("/\\r\\n/", "\n", $string);
	$string = preg_replace("/\\r/", "\n", $string);
	$len = strlen($string);
	$enclosed = false;
	$escape = false;
	$col = 0;
	$i = 0;
	$content = '';
	$row = array();
	$result = array();

	while ($i < $len) {
		$s = $string[$i];

		if ($escape && $s != $enclosure) {
			$escape = false;
		}

		if ($s == $linebreak) {
			if ($enclosed) {
				$content .= $s;
			}
			else {
				$row[$col] = $content;
				$result[] = $row;
				$row = array();
				$content = '';
				$col = 0;
			}
		}
		elseif ($s == $separator) {
			if ($enclosed) {
				$content .= $s;
			}
			else {
				$row[$col] = $content;
				$content = '';
				$col++;
			}
		}
		elseif ($s == $enclosure) {
			if ($escape) {
				$content .= $enclosure;
				$escape = false;
			}

			if ($enclosed) {
				$enclosed = false;
				$escape = true;
			}
			else {
				$enclosed = true;
				$escape = false;
			}
		}
		else {
			$content .= $s;
		}

		$i++;
	}

	return $result;
}