Well, hello community. I'm workin' on a CSV decoder in PHP (yeah, I know there's already one, but as a challenge for me, since I'm learning it in my free time). Now the problem: Well, the rows are split up by PHP_EOL
.
In this line:
foreach(explode($sep, $str) as $line) {
where sep is the variable which splits up the rows and str the string I wanna decode.
But if I wanna split up the columns by a semicolon there might be a situation where a semicolon is content of one column. And as I researched this problem is solved by surrounding the whole column by quote signs like this:
Input:
"0;0";1;2;3;4
Expected output:
0;0 | 1 | 2 | 3 | 4
I already thought of lookahead/lookbehind. But as I didn't use it in past and maybe this could be a good practice for it I don't know how to include it in the regex. My decoding function returns a 2D-array (like a table...) and I thought of adding rows to the array like this (Yep, the regex is f***ed up...):
$res[] = preg_split("/(?<!\")". preg_quote($delim). "(?!\")/", $line);
And at last my full code:
function csv_decode($str, $delim = ";", $sep = PHP_EOL) {
if($delim == "\"") $delim = ";";
$res = [];
foreach(explode($sep, $str) as $line) {
$res[] = preg_split("/(?<!\")". preg_quote($delim). "(?!\")/", $line);
}
return $res;
}
Thanks in advance!
It's a bit counter-intuitive, but the simplest way to split a string by regex is often to use preg_match_all
in place of preg_split
:
preg_match_all('~("[^"]*"|[^;"]*)(?:;|$)~A', $line, $m);
$res[] = $m[1];
The A modifier ensures the contiguity of the successive matches from the start of the string.
If you don't want the quotes to be included in the result, you can use the branch reset feature (?|..(..)..|..(..)..)
:
preg_match_all('~(?|"([^"]*)"|([^;"]*))(?:;|$)~A', $line, $m);
Other workaround, but this time for preg_split
: include the part you want to avoid before the delimiter and discard it from the whole match using the \K
feature:
$res[] = preg_split('~(?:"[^"]*")?\K;~', $line);
You can use this function str_getcsv
in this you can specify a custom delimiter(;
) as well.
<?php
$string='"0;0";1;2;3;4';
print_r(str_getcsv($string,";"));
Output:
Array
(
[0] => 0;0
[1] => 1
[2] => 2
[3] => 3
[4] => 4
)
Split is not a good choice for csv type lines.
You could use the old tried and true \G
anchor with a find globally type func.
Regex: '~\G(?:(?:^|;)\s*)(?|"([^"]*)"|([^;]*?))(?:\s*(?:(?=;)|$))~'
Info:
\G # G anchor, start where last match left off
(?: # leading BOL or ;
(?: ^ | ; )
\s* # optional whitespaces
)
(?| # branch reset
"
( [^"]* ) # (1), double quoted string data
"
| # or
( [^;]*? ) # (1), non-quoted field
)
(?: # trailing optional whitespaces
\s*
(?:
(?= ; ) # lookahead for ;
| $ # or EOL
)
)
来源:https://stackoverflow.com/questions/43427019/splitting-by-a-semicolon-not-surrounded-by-quote-signs