I have the following data in a csv file.
I need to rearrange the data and concate it into 2 columns. the columns will be SKU and Feature. Where SKU = SKU and Feature will be derivative from other columns in the following format.
For yellow marked row: Feature column data will be: Edge:Square Edge;Wide Plank|Finish:Glossy;Smooth|Grade:A(Select & Better/Prestige)|Installation Location:Second Floor;Main Floor........
I could parse the csv and stucked.
$lines = explode( "\n", file_get_contents( '3b.csv' ) );
$headers = str_getcsv( array_shift( $lines ) );
$data = array();
foreach ( $lines as $line ) {
$row = array();
foreach ( str_getcsv( $line ) as $key => $field )
if($headers[$key]=='sku'){
$row[ $headers[ $key ] ] = str_replace(",",";",$field);
}
if($headers[$key]!='sku' && $field!='') {
$row['feature'] = $headers[ $key ].":".str_replace(",",";",$field)."|";
}
$row = array_filter( $row );
$data[] = $row;
}
echo "<pre>";
print_r($data);
echo "</pre>";
Anyone please help me to do this or suggest any script to do this.
You haven't provided the actual text of your incoming csv files, so I will assume that parsing it normally will work properly.
I have borrow my script from your next two questions to unconditionally process your data.
The header row's data is used as a lookup array for the feature names.
Code: (untested)
$file = fopen("3b.csv", "r");
$headers = fgetcsv($file);
$final_array = [];
while (($row = fgetcsv($file)) !== false) {
$sku = $row[0];
unset($row[0]);
foreach ($row as $featureNameIndex => $featureValues) {
foreach (explode(',', $featureValues) as $featureValue) {
$final_array[] = [
'sku' => $sku,
'feature' => "{$headers[$featureNameIndex]}:{$featureValue}"
];
}
}
}
fclose($file);
var_export($final_array);
This approach will generate an indexed array of associative arrays -- each containing two-elements.
Features with multiple values are divided and stored as separate subarrays.
Related
I have a csv file with headers that sometimes have extra fields in a certain row. This is because there was a comma in the text field that was not escaped.
Is there a way to remove a row before converting into array?
Sample csv file:
CUST_NUMBER,PO_NUMBER,NAME,SERVICE,DATE,BOX_NUMBER,TRACK_NO,ORDER_NO,INV_NO,INV_AMOUNT
757626003,7383281,JACK SMITH,GND,20180306,1,1Z1370750453578430,2018168325,119348,70.70
757626003,7383282,GERALD SMITH, JR.,GND,20180306,1,1Z9R67670395033411,2018168326,119513,63.72
757626003,7383233,SCOTT R SMITH,GND,20180306,1,1Z1370750982624042,2018168329,119349,39.33
As you can see, row 3 has an extra field because Gilbert, JR. has a comma in the text field without being escaped which puts the JR. part of the name in the SERVICE column and knocks the GND field outside of the SERVICE column into a column without a heading.
I want to remove the entire row when the row has more fields than there are headers.
After the row is removed I will convert the remaining csv into an array with something like this.
<?
$csv = array_map("str_getcsv", file("FILE.CSV",FILE_SKIP_EMPTY_LINES));
$keys = array_shift($csv);
foreach ($csv as $i => $row) {
if(count($keys) == count($row)){
$csv[$i] = array_combine($keys, $row);
}
}
?>
As suggested by #Scuzzy unset the bad row
<?php
$csv = array_map("str_getcsv", file("FILE.CSV",FILE_SKIP_EMPTY_LINES));
$keys = array_shift($csv);
foreach ($csv as $i => $row) {
if(count($keys) == count($row)){
$csv[$i] = array_combine($keys, $row);
}
else unset($csv[$i]);
}
?>
<?php
$data=<<<DATA
NUMBER,NAME,SERVICE
7375536,Ron,GND
7369530,RANDY,GND
7383287,Gilbert, JR.,GND
7383236,SCOTT,GND
DATA;
$data = array_map('str_getcsv', explode("\n", $data));
$keys = array_shift($data);
$data = array_filter($data, function($v) {
return count($v) == 3;
});
var_export($data);
Output:
array (
0 =>
array (
0 => '7375536',
1 => 'Ron',
2 => 'GND',
),
1 =>
array (
0 => '7369530',
1 => 'RANDY',
2 => 'GND',
),
3 =>
array (
0 => '7383236',
1 => 'SCOTT',
2 => 'GND',
),
)
To use the column headings as keys:
$data = array_map(function($v) use ($keys) {
return array_combine($keys, $v);
}, $data);
Using array_filter allows you to remove the items you don't want by a callback. This version uses the $keys array as the test (same as you use), passing this into the callback using use...
$csv = array_map("str_getcsv", file("books.csv",FILE_SKIP_EMPTY_LINES));
$keys = array_shift($csv);
$output = array_filter($csv, function($row) use ($keys) {
return count($row) == count($keys);
});
$output = array_values($output);
print_r($output);
So each row which doesn't have the same number of columns is removed.
I've just added the array_values() call to re-index the array.
If you could generate the file with surrounding quotes, this problem wouldn't be there...
NUMBER,NAME,SERVICE
7375536,Ron,GND
7369530,RANDY,GND
7383287,"Gilbert, JR.",GND
7383236,SCOTT,GND
You could surround any text field with quotes of your choice to make sure this isn't a problem in the future.
Alternative...
$csv = array_map("str_getcsv", file("FILE.CSV",FILE_SKIP_EMPTY_LINES));
$keys = array_shift($csv);
$out = array();
foreach ($csv as $row) {
if(count($keys) == count($row)){
$out[] = array_combine($keys, $row);
}
}
Last update:
Just while I'm waiting to go out, tried the following. This tries to fix the data, so you get all the rows out of the file...
$out = array();
foreach ($csv as $row) {
if(count($keys) != count($row)){
$row = array_merge(array_slice($row, 0, 2),
[implode(",", array_slice($row, 2, count($row)-9))],
array_slice($row, count($row)-7));
}
$out[] = array_combine($keys, $row);
}
I have multiple arrays, where I want to name each array at the end of the for each loop.
The code php code:
<?php
$csv = array_map("str_getcsv", file("translations/dk.csv"));
foreach ($csv as $line){
if ($line[1] != NULL){
$line[0] = $line[1];
}
print_r($line[0]);
print_r("<br />");
}
fclose($csv);
?>
Example of the arrays.
Array ( [0] => Search and Save [1] => Søg og Spar på Hoteller )
Array ( [0] => Where are you going? [1] => Hvor skal du hen? )
Now the output of line[0] is each time the foreach loop runs naturally with a different value. But I need to name each $line[0] on every loop so I can access them afterwards. How do I do this ?
First of all, you shouldn't use file() to split up the lines; CSV records may span multiple lines.
$f = fopen("translations/dk.csv", 'rt');
$csv = array();
while (($data = fgetcsv($f)) !== false) {
$csv[] = $data;
}
fclose($f);
Second, to select the first column from the array you can use array_column():
$results = array_column($csv, 0);
// "Search and Save", "Where are you going?"]
Maybe you're looking for something like this:
<?php
$csv = array_map("str_getcsv", file("translations/dk.csv"));
$results = array();
foreach ($csv as $k => $line){
if ($line[1] != NULL){
$line[0] = $line[1];
$results[$k] = $line[0];
}
print_r($line[0]);
print_r("<br />");
}
print_r($results);
?>
Is this iteration key by key the proper way of initializing custom named keys for my str_getcsv array values ?
Also is the unset() expensive or even necessary in this loop ?
<?php
$lines = file('csv/random.csv', FILE_IGNORE_NEW_LINES);
foreach ($lines as $key => $value)
{
$temp = str_getcsv($value,'|');
$csv[$key]['code'] = $temp[0];
$csv[$key]['name'] = $temp[1];
$csv[$key]['price'] = $temp[2];
$csv[$key]['avail'] = $temp[3];
unset ($temp);
}
?>
EDIT:
Following the advice in comments the code looks neater and runs significantly faster now
$keys = Array('code','name','price','avail');
$file = fopen('csv/random.csv',"r");
while(! feof($file))
{
$temp[] = array_combine($keys,fgetcsv($file,'1000','|'));
}
fclose($file);
The answer marked below is I believe a more professional implementation of this for those who have need of such.
Thanks guys.
You might want to use SplFileObject class:
<?php
$file = new SplFileObject('csv/random.csv');
$file->setFlags(SplFileObject::READ_CSV | SplFileObject::SKIP_EMPTY
| SplFileObject::DROP_NEW_LINE | SplFileObject::READ_AHEAD);
$file->setCsvControl('|');
$data = array();
foreach ($file as $row) {
$data[] = array_combine(array('code', 'name', 'price', 'avail'), $row);
}
You have to be sure that your CSV lines have 4 different fields to use array_combine() this way. Note that this will give you all your CSV fields as strings. If you want, for instance, to have code as integer, name as string, price as float and avail as integer for each row, you can replace the foreach with that:
foreach ($file as $row) {
$data[] = array_combine(
array('code', 'name', 'price', 'avail'),
array((int) $row[0], $row[1], (float) $row[2], (int) $row[3])
);
}
To apply int/float conversions you can even use the fscanf() method to read lines:
<?php
$file = new SplFileObject('csv/random.csv');
$file->setFlags(SplFileObject::SKIP_EMPTY | SplFileObject::DROP_NEW_LINE
| SplFileObject::READ_AHEAD);
$data = array();
while ($row = $file->fscanf('%d|%[^|]|%f|%d')) {
$data[] = array_combine(array('code', 'name', 'price', 'avail'), $row);
}
I have created an array using
$processed[$y] = array('source' => $source,
'check total' => number_format($checkTotal, 2, '.', ''),//($rows['total'], 2, '.', ''),
'check number' => $num,
'table' => $invTable,
'skus' => $skuArray,
'check amount' => number_format($amount, 2, '.', '')
);
$y++;
My $skuArray is an array that contains all of the sku's that are associated with a specific check number. I am attempting to have it displayed like:
source check total check number table skus check amount
MNC 152.32 649 inv_temp 10198547 152.32
10195874
so it will list all of the sku's attached to a specific check nuimber before it lists the next item.
Here is my function to convert $processed to a csv file:
function to_csv( $array ) {
$csv = "";
if (count($array) == 0) return "No Processed checks found";
## Grab the first element to build the header
$arr = array_pop( $array );
$temp = array();
foreach( $arr as $key => $data ) {
$temp[] = $key;
}
$csv = implode( ',', $temp ) . "\r\n";
## Add the data from the first element
$csv .= to_csv_line( $arr );
## Add the data for the rest
foreach( $array as $arr ) {
$csv .= to_csv_line( $arr );
}
return $csv;
}
function to_csv_line( $array ) {
$temp = array();
foreach( $array as $elt ) {
$temp[] = '"' . addslashes( $elt ) . '"';
}
$string = implode( ',', $temp ) . "\r\n";
return $string;
}
How can I accomplish this? I have tried using array('skus=>$skuArray), but it just gave me "Array" in the results.
UPDATE: Here is what the array looks like when I do a var_dump($skuArray)
array(1075) { [0]=> string(8) "10182997" [1]=> string(8) "10190313" [2]=> string(8) "10190314" [3]=> string(8) "10190315" etc.
I've provided a solution that is untested, so use at your own discretion.
I've done my best to explain everything through comments in the code.
Remove the first sku value from the sku array, assign it to the first line.
Add additional skus to a temporary array based on the line keys.
Check for temporary sku array, and create the additional lines from it.
Your final to_csv function will look something like this:
function to_csv( $array ) {
$csv = "";
if (count($array) == 0) return "No Processed checks found";
## Grab the first element to build the header
$arr = $array[0];
$temp = array();
foreach( $arr as $key => $data ) {
$temp[] = $key;
}
$csv = implode( ',', $temp ) . "\r\n";
## Process each line
foreach( $array as $arr ) {
## Check for multiple sku values. Create a temporary array for them to add them after this line.
if(isset($arr['skus']) && is_array($arr['skus']))
{
//Remove the first value (since we only need it for the actual line item)
$sku_value = $arr['skus'][0];
unset($arr['skus'][0]);
//Create temporary lines for each sku
$temp_sku_arrays = array();
foreach($arr['skus'] as $sku)
{
$sku_array = array();
foreach($arr as $key => $value)
{
//Set only the sku key with a value.
$sku_array[$key] = ($key == 'skus' ? $sku : '');
}
$temp_sku_arrays[] = $sku_array;
}
//Set the first line to the first sku value.
$arr['skus'] = $sku_value;
}
$csv .= to_csv_line( $arr );
//Check for additional sku lines, then add them
if(isset($temp_sku_arrays) && is_array($temp_sku_arrays))
{
foreach($temp_sku_arrays as $sku_array)
{
$csv .= to_csv_line( $sku_array );
}
unset($temp_sku_arrays);
}
}
return $csv;
}
I think CSV is not well suited for what you are about to do. I would use json or xml. However, you could choose a separator different from the csv sepator to represent an array, Like this:
foo,bar1;bar2;bar3,...
what would represent the following record:
$record = array (
'foo',
array ('bar1', 'bar2', 'bar3')
);
I have csv values like this:
$csv_data = "test,this,thing
hi,there,this
is,cool,dude
have,fun";
I want to take an entire CSV string and read it into a multidemensional array so that I get:
array(
array(
'test' => 'hi',
'this' => 'there',
'thing' => 'this'
),
array(
'test' => 'is',
'this' => 'cool',
'thing' => 'dude'
),
array(
'test' => 'have',
'this' => 'fun',
'thing' => ''
)
);
I want an output like that, take note that the CSV value is dynamic.
Assuming every row in the CSV data has the same number of columns, this should work.
$lines = explode("\n", $csv_data);
$head = str_getcsv(array_shift($lines));
$array = array();
foreach ($lines as $line) {
$array[] = array_combine($head, str_getcsv($line));
}
If lines have a variable number of columns (as in your example, where the last line has 2 columns instead of 3), use this loop instead:
foreach ($lines as $line) {
$row = array_pad(str_getcsv($line), count($head), '');
$array[] = array_combine($head, $row);
}
Here is a complete solution:
$lines = explode("\n", $csv_data);
$formatting = explode(",", $lines[0]);
unset($lines[0]);
$results = array();
foreach ( $lines as $line ) {
$parsedLine = str_getcsv( $line, ',' );
$result = array();
foreach ( $formatting as $index => $caption ) {
if(isset($parsedLine[$index])) {
$result[$formatting[$index]] = trim($parsedLine[$index]);
} else {
$result[$formatting[$index]] = '';
}
}
$results[] = $result;
}
So what are we doing here?
First, your CSV data is split into array of lines with explode
Since the first row in your CSV describes data format, it must be separated from the actual data rows (explode and unset)
For storing the results, we initialize a new array ($results)
Foreach is used to iterate through the data line by line. For each line:
Line is parsed with PHP's str_getcsv
An empty result array is initialized
Each line is inspected in the light of the format. Cells are added and missing columns are padded with empty strings.
Here is a very clean and simple solution:
function parse_row($row) {
return array_map('trim', explode(',', $row));
}
$rows = str_getcsv($csv_data, "\n");
$keys = parse_row(array_shift($rows));
$result = array();
foreach ($rows as $row) {
$row = parse_row($row);
$row = array_pad($row, 3, NULL);
$result[] = array_combine($keys, $row);
}