PHP - Array to CSV by Column - php

issue: I have an associative array, where all the keys represent the csv headers and the values in each $key => array.. represent the items in that column.
Research: To my knowledge, fputcsv likes to go in row by row, but this column-based array is making that complicated. I have not found any function out there that accomplishes this.
Example:
Array(
['fruits'] => Array(
[0] => 'apples',
[1] => 'oranges',
[2] => 'bananas'
),
['meats'] => Array(
[0] => 'porkchop',
[1] => 'chicken',
[2] => 'salami',
[3] => 'rabbit'
),
)
Needs to become:
fruits,meats
apples,porkchop
oranges,chicken
bananas,salami
,rabbit
Why it's hard:
You need to know the max number of rows to make blank spots.

I had to write my own function. Thought maybe it might help someone else!
/*
* The array is associative, where the keys are headers
* and the values are the items in that column.
*
* Because the array is by column, this function is probably costly.
* Consider a different layout for your array and use a better function.
*
* #param $array array The array to convert to csv.
* #param $file string of the path to write the file.
* #param $delimeter string a character to act as glue.
* #param $enclosure string a character to wrap around text that contains the delimeter
* #param $escape string a character to escape the enclosure character.
* #return mixed int|boolean result of file_put_contents.
*/
function array_to_csv($array, $file, $delimeter = ',', $enclosure = '"', $escape = '\\'){
$max_rows = get_max_array_values($array);
$row_array = array();
$content = '';
foreach ($array as $header => $values) {
$row_array[0][] = $header;
$count = count($values);
for ($c = 1; $c <= $count; $c++){
$value = $values[$c - 1];
$value = preg_replace('#"#', $escape.'"', $value);
$put_value = (preg_match("#$delimeter#", $value)) ? $enclosure.$value.$enclosure : $value;
$row_array[$c][] = $put_value;
}
// catch extra rows that need to be blank
for (; $c <= $max_rows; $c++) {
$row_array[$c][] = '';
}
}
foreach ($row_array as $cur_row) {
$content .= implode($delimeter,$cur_row)."\n";
}
return file_put_contents($file, $content);
}
And this:
/*
* Get maximum number of values in the entire array.
*/
function get_max_array_values($array){
$max_rows = 0;
foreach ($array as $cur_array) {
$cur_count = count($cur_array);
$max_rows = ($max_rows < $cur_count) ? $cur_count : $max_rows;
}
return $max_rows;
}
New Way (using a class)
I wrote a class for this a while later, which I'll provide for anyone looking for one now:
class CSVService {
protected $csvSyntax;
public function __construct()
{
return $this;
}
public function renderCSV($contents, $filename = 'data.csv')
{
header('Content-type: text/csv');
header('Content-Disposition: attachment; filename="' . $filename . '"');
echo $contents;
}
public function CSVtoArray($filename = '', $delimiter = ',') {
if (!file_exists($filename) || !is_readable($filename)) {
return false;
}
$headers = null;
$data = array();
if (($handle = fopen($filename, 'r')) !== false) {
while (($row = fgetcsv($handle, 0, $delimiter, '"')) !== false) {
if (!$headers) {
$headers = $row;
array_walk($headers, 'trim');
$headers = array_unique($headers);
} else {
for ($i = 0, $j = count($headers); $i < $j; ++$i) {
$row[$i] = trim($row[$i]);
if (empty($row[$i]) && !isset($data[trim($headers[$i])])) {
$data[trim($headers[$i])] = array();
} else if (empty($row[$i])) {
continue;
} else {
$data[trim($headers[$i])][] = stripcslashes($row[$i]);
}
}
}
}
fclose($handle);
}
return $data;
}
protected function getMaxArrayValues($array)
{
return array_reduce($array, function($carry, $item){
return ($carry > $c = count($item)) ? $carry : $c;
}, 0);
}
private function getCSVHeaders($array)
{
return array_reduce(
array_keys($array),
function($carry, $item) {
return $carry . $this->prepareCSVValue($item) . $this->csvSyntax->delimiter;
}, '') . "\n";
}
private function prepareCSVValue($value, $delimiter = ',', $enclosure = '"', $escape = '\\')
{
$valueEscaped = preg_replace('#"#', $escape . '"', $value);
return (preg_match("#$delimiter#", $valueEscaped)) ?
$enclosure . $valueEscaped . $enclosure : $valueEscaped;
}
private function setUpCSVSyntax($delimiter, $enclosure, $escape)
{
$this->csvSyntax = (object) [
'delimiter' => $delimiter,
'enclosure' => $enclosure,
'escape' => $escape,
];
}
private function getCSVRows($array)
{
$n = $this->getMaxArrayValues($array);
$even = array_values(
array_map(function($columnArray) use ($n) {
for ($i = count($columnArray); $i <= $n; $i++) {
$columnArray[] = '';
}
return $columnArray;
}, $array)
);
$rowString = '';
for ($row = 0; $row < $n; $row++) {
for ($col = 0; $col < count($even); $col++) {
$value = $even[$col][$row];
$rowString .=
$this->prepareCSVValue($value) .
$this->csvSyntax->delimiter;
}
$rowString .= "\n";
}
return $rowString;
}
public function arrayToCSV($array, $delimiter = ',', $enclosure = '"', $escape = '\\', $headers = true) {
$this->setUpCSVSyntax($delimiter, $enclosure, $escape);
$headersString = ($headers) ? $this->getCSVHeaders($array) : '';
$rowsString = $this->getCSVRows($array);
return $headersString . $rowsString;
}
}

$data = array(
'fruits' => array(
'apples',
'oranges',
'bananas'
),
'meats' => array(
'porkchop',
'chicken',
'salami',
'rabbit'
),
);
$combined = array(array('fruits', 'meats'));
for($i = 0; $i < max(count($data['fruits']), count($data['meats'])); $i++)
{
$row = array();
$row[] = isset($data['fruits'][$i]) ? $data['fruits'][$i] : '';
$row[] = isset($data['meats'][$i]) ? $data['meats'][$i] : '';
$combined[] = $row;
}
ob_start();
$fp = fopen('php://output', 'w');
foreach($combined as $row)
fputcsv($fp, $row);
fclose($fp);
$data = ob_get_clean();
var_dump($data);
Conversion to csv and parsing of the array can be done in the same loop. Or did you mean that there might be more columns? This code can be easily modified for the general type of the array. Like this, for any number of the columns
$data = array(
'fruits' => array(
'apples',
'oranges',
'bananas'
),
'meats' => array(
'porkchop',
'chicken',
'salami',
'rabbit'
),
);
$heads = array_keys($data);
$maxs = array();
foreach($heads as $head)
$maxs[] = count($data[$head]);
ob_start();
$fp = fopen('php://output', 'w');
fputcsv($fp, $heads);
for($i = 0; $i < max($maxs); $i++)
{
$row = array();
foreach($heads as $head)
$row[] = isset($data[$head][$i]) ? $data[$head][$i] : '';
fputcsv($fp, $row);
}
fclose($fp);
$data = ob_get_clean();
var_dump($data);

Related

get_query_var mergin two query variable

I registered two query varaible 'star' & 'cord'
And when I visit https://website.com/blog/star-4-5/cord-Corded/
and use $star = explode( '-', get_query_var('star', '') );
$star output is shows as
Array ( [0] => 4 [1] => 5/cord [2] => Corded )
and $cord output is shows as 'Empty'
While $star should show Array ( [0] => 4 [1] => 5 )
and $cord should show 'Corded '
function trimmer_register_query_vars( $vars ) {
$vars[] = 'brand';
$vars[] = 'price';
$vars[] = 'water';
$vars[] = 'star';
$vars[] = 'cord';
return $vars;}add_filter( 'query_vars', 'trimmer_register_query_vars' );
Here's written rule I used for it, it automatically rewrite a combination of all queries.
function generate_rewrite_rules($keys) {
$total_keys = count($keys);
$rules = array();
for ($i = 1; $i <= $total_keys; $i++) {
$combinations = combinations($keys, $i);
foreach ($combinations as $combination) {
$rule = '^trimmer/';
$query = 'index.php?post_type=trimmer&';
$j = 1;
foreach ($combination as $key) {
$rule .= $key . '-(.+?)/'; // Use non-greedy match (`.+?`) instead of greedy match (`.+`)
$query .= $key . '=$matches[' . $j . ']&';
$j++;
}
$rule = rtrim($rule, '/');
$rule .= '/?$';
$query = rtrim($query, '&');
$rules[] = array(
'rule' => $rule,
'query' => $query,
'top' => true
);
}
}
return $rules;
}
function combinations($keys, $length) {
if ($length == 1) {
return array_map(function($val) {
return array($val);
}, $keys);
}
$result = array();
for ($i = 0; $i < count($keys) - $length + 1; $i++) {
$head = $keys[$i];
$combos = combinations(array_slice($keys, $i + 1), $length - 1);
foreach ($combos as $combo) {
array_unshift($combo, $head);
$result[] = $combo;
}
}
return $result;
}
add_action('init', 'register_trimmer_rules');
function register_trimmer_rules() {
$keys = array('brand', 'price', 'cord', 'water', 'star');
$rules = generate_rewrite_rules($keys);
foreach ($rules as $rule) {
add_rewrite_rule($rule['rule'], $rule['query'], $rule['top']);
}
}

How to split a php array into several parts to insert them in csv column names?

I wrote a code that allows me to read an excel file, transform it into csv and make modifications on it. I add at the end of this file 10 columns, the problem is that the name of the columns is added at once with "PICT1;PICT2; PICT3;PICT4;..." in only one column at the end of the 10. I would like each column to be called PICT1, PICT2, PICT3... How do I do this?
Currently I have this:
I would like that:
<?php
function multiSplit($string)
{
$output = array();
$cols = explode(",", $string);
foreach ($cols as $col)
{
$dashcols = explode("-", $col);
$output[] = $dashcols[0];
}
return $output;
}
//Modifications on csv file
$delimiter = $delimiterpost;
$csv_data = array();
$row = 1;
if (($handle = fopen($nomcsv, 'r')) !== FALSE) {
while (($data = fgetcsv($handle, 10000, $delimiter)) !== FALSE) {
//Add columns at the end
$data['Pictures Names'] = (!empty($data[4]) ? ($data[7] ?: '') . "_" . $data[4] . '.jpg' : '');
$data['Color-Description'] = (!empty($data[3]) ? (ltrim($data[4], '0') ?: '') . "-" . $data[3] : '');
$out = multiSplit($data[23]);
$data['PICT1'] = $out[0];
$data['PICT2'] = $out[1];
$data['PICT3'] = $out[2];
$data['PICT4'] = $out[3];
$data['PICT5'] = $out[4];
$data['PICT6'] = $out[5];
$data['PICT7'] = $out[6];
$data['PICT8'] = $out[7];
$data['PICT9'] = $out[8];
$data['PICT10'] = $out[9];
// Delete three columns
unset($data[1]);
unset($data[2]);
unset($data[3]);
//Modifications on the fourth line
if ($row == 4)
{
//All uppercase
$data = array_map('strtoupper', $data);
$data = str_replace(' *', '', $data);
$data = str_replace('/', '', $data);
//Replacement of spaces by an underscore
$data = str_replace(' ', '_', $data);
$data = str_replace('__', '_', $data);
//Replacement name by another name
$data = str_replace('STYLE_COLOR.JPG', 'PICT', $data);
$data = str_replace('COLOR-DESCRIPTION', 'COLOR_DESCRIPTION', $data);
array_filter($data, function($value) { return !is_null($value) && $value !== ''; });
for ( $i=1; $i<=10; $i++ ){
$data[] = "PICT$i";
}
}
if ($data[22])
{
$data = str_replace(',', 'ยง- ', $data);
}
$csv_data[] = $data;
$row++;
}
fclose($handle);
}
$csv_data = array_slice($csv_data, 3); // this will remove first three elements
array_pop($csv_data);// this will remove last element from array
if (($handle = fopen($nomcsv, 'w')) !== FALSE) {
foreach ($csv_data as $data) {
fputcsv($handle, $data, $delimiter);
}
fclose($handle);
}
Replace
$data[] ='PICT1'.$delimiter.'PICT2'.$delimiter.'PICT3'.$delimiter.'PICT4'.$delimiter.'PICT5'.$delimiter.'PICT6'.$delimiter.'PICT7'.$delimiter.'PICT8'.$delimiter.'PICT9'.$delimiter.'PICT10';
With a loop that creates the additional array items
// clean out any empty titles as there appear to be some at the end of the array
array_filter($data, function($value) { return !is_null($value) && $value !== ''; });
for ( $i=1; $i<=10; $i++ ){
$data[] = "PICT$i";
}

PHP for loop stops too early

I have a loop which increments the limit of the content which is showing on a website (json content).
The problem is that the loop stops at 50. The links that are generated with the incrementation are working fine when I'm calling them in the browser. The content is shown.
But I store only the content of the first 50 and then it stops.
public static function getAllCustomers() {
$rest = Rest::getInstance();
$spaces = self::getSpaces();
$customers = array();
$path = SpacePath::buildPath();
for ($i = 0;
; $i += Paths::$MAX_CONTENT_LENGTH) {
$temp = $path . '?start=' . $i . '&limit=50';
$content = Helper::toArray($temp);
print_r($temp);
// print_r(empty($content['results']));
if (!empty($content['results'])) {
foreach ($content ['results'] as $space) {
if (!preg_match('|^(.*?)-([0-9]+)|i', $space ['name'], $matches)) {
continue;
}
$customer = (object) array(
'name' => $matches [0],
'ident' => $matches [1],
'id' => $matches [2],
'space_key' => $space ['key'],
'options' => array()
);
$customers [] = $customer;
}
break;
}
}
print_r($customers);
return $customers;
}
Here, let me reformat your code so you can see what relates to what...
public static function getAllCustomers()
{
$rest = Rest::getInstance();
$spaces = self::getSpaces();
$customers = array();
$path = SpacePath::buildPath();
for ($i = 0; ; $i += Paths::$MAX_CONTENT_LENGTH)
{
$temp = $path . '?start=' . $i . '&limit=50';
$content = Helper::toArray($temp);
print_r($temp);
//print_r(empty($content['results']));
if (!empty($content['results']))
{
foreach ($content ['results'] as $space)
{
if (!preg_match('|^(.*?)-([0-9]+)|i', $space ['name'], $matches))
{
continue;
}
$customer = (object) array('name' => $matches [0]
,'ident' => $matches [1]
,'id' => $matches [2]
,'space_key' => $space ['key']
,'options' => array()
);
$customers [] = $customer;
}
break; // oops
}
}
print_r($customers);
return $customers;
}
You'll notice I added a comment to your break statement. ;)

How do I put this array into csv or excel?

I can't figure out a way to do this and I really bad with working with arrays so I hope someone can help me.
I have array $stuck that just contains names, like this
$stuck = array('Daniel','Alex','alfredo','dina');
I am using sort to put the names in alphabetical order, like this
sort($stuck);
Now the thing is I want to put this array into a csv or excel file so the first letter of name will be the title and all the names with this letter will be under this title.
This is an example of what I am trying to accomplish
Here try this. I added comments between the code.
$csvArray = array();
$nameArray = array('Daniel','Alex','alfredo','dina');
//Create an array that can be used for creating the CSV
foreach($nameArray as $name)
{
$firstLetter = strtoupper(substr($name, 0,1));
$csvArray[$firstLetter][] = $name;
}
//Determine the subarray which have the must rules
$maxRuleCount = 0;
foreach($csvArray as $firstLetter => $nameArray)
{
if(count($nameArray) > $maxRuleCount)
{
$maxRuleCount = count($nameArray);
}
}
//Create the first rule (letters)
$csvContent = implode(';', array_keys($csvArray));
$csvContent .= "\r\n";
//Loop through the CSV array and create rules of the names
for($i = 0 ; $i < $maxRuleCount ; $i++)
{
foreach($csvArray as $firstLetter => $nameArray)
{
$csvContent .= #$csvArray[$firstLetter][$i].';';
}
$csvContent .= "\r\n";
}
//The hole csv content is in the $csvContent variable now
//If you want to print it you need to add the text/plain header because of the \r\n new line characters
header("Content-Type:text/plain");
echo $csvContent;
Ouput is:
D;A
Daniel;Alex;
dina;alfredo;
<?php
$stuck = array('Daniel','Alex','Dina','durban','eigor','alfredo','dina');
// associate names with first letters in a new array
foreach ($stuck as $name) {
$first_letter = strtoupper( substr( $name, 0, 1 ) );
$new_name_array[ $first_letter ][] = $name;
}
// store the first letter list in an array and sort it
$first_letters = array_keys( $new_name_array );
sort( $first_letters );
// sort the name lists
foreach( array_keys( $new_name_array ) as $letter ) {
sort( $new_name_array[$letter] );
}
// output csv header row
echo "'".implode( "','", $first_letters )."'\n";
// output the CSV name rows
$i = 0;
while ( true ) {
$row = array();
$is_row = false;
foreach( $first_letters as $letter ) {
$row[] = $new_name_array[ $letter ][ $i ];
if( ! empty( $new_name_array[ $letter ][ $i ] ) ) $is_row = true;
}
if( ! $is_row ) exit;
echo "'" . implode( "','", $row ) . "'\n";
$i++;
}
?>
Outputs quoted CSV:
'A','D','E'
'Alex','Daniel','eigor'
'alfredo','Dina',''
'','dina',''
'','durban',''
<?php
$stuck = array('Daniel','Alex','alfredo','dina', 'eigor', 'durban');
sort($stuck);
$sortedNames = array();
$maxEl = 0;
foreach ($stuck as $name) {
$name = strtolower($name);
$sortedNames[$name{0}][] = $name;
$maxEl = count($sortedNames[$name{0}]) < $maxEl ? $maxEl : count($sortedNames[$name{0}]);
}
$headers = array_keys($sortedNames);
$finalArray = array($headers);
for($i = 0; $i < $maxEl; $i++) {
$tmpRow = array();
foreach ($sortedNames as $names) {
$tmpRow[] = isset($names[$i]) ? $names[$i] : '';
}
$finalArray[] = $tmpRow;
}
$file = fopen("names.csv","w");
foreach ($finalArray as $line)
{
fputcsv($file,$line);
}
fclose($file);
Here is my attempt at this question:
$names = array('Daniel','Alex','alfredo','dina', 'eigor', 'falfoul', 'fiona');
natcasesort($names);
$columns = array();
foreach ($names as $name) {
$first = strtoupper(substr($name, 0, 1));
$columns[$first][] = $name;
}
// pad the columns so they all have the same number of rows
$maxRows = 0;
foreach ($columns as &$col) {
$numRows = count($col);
if ( $numRows > $maxRows) {
$maxRows = $numRows;
}
}
// pad the columns
foreach ($columns as &$column) {
$column = array_pad($column, $maxRows, '');
}
$firstLetter = array_keys($columns);
$numCols = count($firstLetter);
// transpose the columns array
$rows = array();
foreach ($columns as $csvCol) {
foreach ($csvCol as $k=>$n) {
$rows[$k][] = $n;
}
}
// pad the rows to ensure they all have the same number of
// columns
foreach ($rows as &$row) {
$row = array_pad($row, $numCols, '');
}
// add the title row to the rows
array_unshift($rows, $firstLetter);
$handle = fopen("names.csv", 'w');
foreach ($rows as $fRow) {
fputcsv($handle, $fRow);
}

Multiple String Replace Based on Index

I need to replace multiple sections of a string based on their indices.
$string = '01234567890123456789';
$replacements = array(
array(3, 2, 'test'),
array(8, 2, 'haha')
);
$expected_result = '012test567haha0123456789';
Indices in $replacements are expected not to have overlaps.
I have been trying to write my own solution, split the original array into multiple pieces based on sections which needs to be replaced or not, and finally combine them:
echo str_replace_with_indices($string, $replacements);
// outputs the expected result '012test567haha0123456789'
function str_replace_with_indices ($string, $replacements) {
$string_chars = str_split($string);
$string_sections = array();
$replacing = false;
$section = 0;
foreach($string_chars as $char_idx => $char) {
if ($replacing != (($r_idx = replacing($replacements, $char_idx)) !== false)) {
$replacing = !$replacing;
$section++;
}
$string_sections[$section] = $string_sections[$section] ? $string_sections[$section] : array();
$string_sections[$section]['original'] .= $char;
if ($replacing) $string_sections[$section]['new'] = $replacements[$r_idx][2];
}
$string_result = '';
foreach($string_sections as $s) {
$string_result .= ($s['new']) ? $s['new'] : $s['original'];
}
return $string_result;
}
function replacing($replacements, $idx) {
foreach($replacements as $r_idx => $r) {
if ($idx >= $r[0] && $idx < $r[0]+$r[1]) {
return $r_idx;
}
}
return false;
}
Is there any more effective way to achieve the same result?
The above solution doesn't look elegant and feels quite long for string replacement.
Use this
$str = '01234567890123456789';
$rep = array(array(3,3,'test'), array(8,2,'haha'));
$index = 0;
$ctr = 0;
$index_strlen = 0;
foreach($rep as $s)
{
$index = $s[0]+$index_strlen;
$str = substr_replace($str, $s[2], $index, $s[1]);
$index_strlen += strlen($s[2]) - $s[1];
}
echo $str;

Categories