I have a csv file like this:-
+------+------+------------------------+
| name | mark | url |
+------+------+------------------------+
| ABCD | 5 | http://www.example.org |
| BCD | -2 | http://www.example.com |
| CD | 4 | htt://www.c.com |
+------+------+------------------------+
It contains a header for name, mark and url. I am using PHP to convert the data from csv to json. I want to add validation before converting it into json like for the name it should be in UTF-8, the mark should be a positive number and between 0 to 5 and the url should be valid. If the row passes all validation then it gets stored in a errorless.json and if any row has any issues then in error.json with a comment what was wrong. PHP code for csv to json:-
$fh = fopen("names.csv", "r");
$csvdata = array();
while (($row = fgetcsv($fh, 0, ",")) !== FALSE) {
$csvdata[] = $row;
}
$fp = fopen('data.json', 'w');
fwrite($fp, json_encode($csvdata));
fclose($fp);
I wanted to know how can i add these validations for converting the data. I am new to these concepts and unable to think of a way to do it. I would be highly grateful if anyone can help me.
Here is a shell of what you would do:
// to make life easier on yourself create
// a function that checks one row of data
// to make sure it is valid
// if a row is found to be invalid, the
// function will add an `error` field to the
// array explaining the validation error
function validateRow(&$data) {
if (!array_key_exists('mark', $data) && ((int)$data['mark']) >= 0 && ((int)$data['mark']) <= 5) {
$data['error'] = "No 'mark' value found between 0 and 5";
return false;
}
// do validation for $data['name']
// do validation for $data['url']
return true;
}
$fh = fopen("names.csv", "r");
$csvdata = array();
while (($row = fgetcsv($fh, 0, ",")) !== FALSE) {
$csvdata[] = $row;
}
$header = $csvdata[0];
$n = count($header);
$errorless = array();
$haserrors = array();
// here is where we convert the CSV data into
// an associative array / map of key value
// pairs by treating each row and the header
// row as parallel arrays. Start with index 1
// in the for loop to skip over the header row
// in csvdata
for ($row = 1; $row < count($csvdata); ++$row) {
$data = array();
for ($x = 0; $x < $n; ++$x) {
$data[$header[$x]] = $csvdata[$row][$x];
}
// if we encounter no errors,
// add data to the errorless array
if (validateRow($data)) {
$errorless[] = $data;
}
else {
$haserrors[] = $data;
}
}
// you will want to do a parallel write
// for the error.json file
$fp = fopen('errorless.json', 'w');
fwrite($fp, json_encode($errorless));
fclose($fp);
You should use something called JSON schema. You define how your document should be formed. Then your document can be validated automatically.
In case you were looking for another concrete implementation of CSV validation libraries, you should check out packagist.org in the first place.
This may help you,
The standard JSON format doesn't explicitly support file comments. RFC 4627 application/json, It's a lightweight format for storing and transferring data. If the comment is truly important, you can include it as another data field like comments
Input
$ cat test.csv
name,mark,url
ABCD,5,http://www.example.org
BCD,-2,http://www.example.com
CD,4,htt://www.c.com
Script
<?php
function validate_url($url)
{
return in_array(parse_url($url, PHP_URL_SCHEME),array('http','https')) && filter_var($url, FILTER_VALIDATE_URL);
}
function validate_mark($val)
{
return ($val >= 0 && $val <= 5);
}
function errors($field)
{
$errors = array(
'url' => 'URL should be valid',
'mark' => 'Mark should be between 0 to 5'
);
return ( isset($errors[$field]) ? $errors[$field] : "Unknown");
}
function csv2array_with_validation($filename, $delimiter = ",")
{
$header = $row = $c_row = $output = $val_func = array();
if (($handle = fopen($filename, 'r')) !== FALSE)
{
while (($row = fgetcsv($handle, 0, $delimiter)) !== FALSE)
{
if (empty($header))
{
$header = array_map('strtolower', $row);
foreach ($header as $e)
{
$val_func[$e] = function_exists('validate_' . $e);
}
continue;
}
$c_row = array_combine($header, $row);
$index = 'error_less'; $errors = array();
foreach ($c_row as $e => $v)
{
if ($val_func[$e])
{
if (!call_user_func('validate_' . $e, $v))
{
$index = 'error';
$errors[$e] = errors($e);
}
}
}
/*
If the comment is truly important,
you can include it as another data field like errors,
comment below part if you do not wish to create new field (errors) in
json file
*/
if(!empty($errors))
{
$c_row['errors'] = $errors;
}
$output[$index][] = $c_row;
}
fclose($handle);
}
return $output;
}
$output = csv2array_with_validation('test.csv');
// Write error.json
if (isset($output['error']) && !empty($output['error']))
{
file_put_contents('error.json', json_encode($output['error'], JSON_PRETTY_PRINT | JSON_NUMERIC_CHECK));
}
// Write errorless.json
if (isset($output['error_less']) && !empty($output['error_less']))
{
file_put_contents('error_less.json', json_encode($output['error_less'], JSON_PRETTY_PRINT | JSON_NUMERIC_CHECK));
}
?>
Output
$ php test.php
$ cat error.json
[
{
"name": "BCD",
"mark": -2,
"url": "http:\/\/www.example.com",
"errors": {
"mark": "Mark should be between 0 to 5"
}
},
{
"name": "CD",
"mark": 4,
"url": "htt:\/\/www.c.com",
"errors": {
"url": "URL should be valid"
}
}
]
$ cat error_less.json
[
{
"name": "ABCD",
"mark": 5,
"url": "http:\/\/www.example.org"
}
]
Related
I'm trying to make a translation module for my web project and i hit a wall. I written two functions, one for importing the CSV data and the other for changing, deleting and adding new data to the CSV file.
Everything works like a charm except the adding part. It adds the data to the CSV file but when i want to import it in the website via PHP it doesn't display the added values. (It see that their should be values but it gives empty results in return) the function i use for reading the csv file is:
// Load in CSV (File name, delimiter, Fixed array[key,value] (optional))
function load__CSV($filename='', $delimiter=';', $fixed = null){
if(!file_exists($filename) || !is_readable($filename)) {
return FALSE;
}
$header = NULL;
$data = array();
if (($handle = fopen($filename, 'r')) !== FALSE) {
while (($row = fgetcsv($handle, 100000, $delimiter)) !== FALSE) {
if (!$header) {
$header = $row;
} else {
$data[] = array_combine($header, $row);
}
}
fclose($handle);
}
if($fixed != null) {
foreach($data as $entry){
$set_csv[$entry[''.$fixed[0].'']] = $entry[''.$fixed[1].''];
}
} else {
$set_csv = $data;
}
return $set_csv;
}
The function i use to add, edit or remove CSV content is:
// Change csv
function update__csv($csv = 'csv/language.csv',$key = array(2,''),$values = array([3,''],[4,'']),$status = 'change') {
$input = fopen(BASE_URL.$csv, 'r');
$output = fopen(BASE_URL.'csv/temporary.csv', 'w');
while (false !== ($data = fgetcsv($input, 10000, ";"))) {
if ($data[$key[0]] == $key[1]) {
foreach ($values as $value) {
$data[$value[0]] = $value[1];
}
if ($status == 'change' || $status == 'new') {
fputcsv($output, $data, ";");
}
} else {
fputcsv($output, $data, ";");
}
}
if($status == 'new'){
fputcsv($output, $values, ";");
}
fclose( $input );
fclose( $output );
unlink(BASE_URL . $csv);
rename(BASE_URL . 'csv/temporary.csv', BASE_URL . $csv);
}
If i add new values to the CSV file and then open the CSV on my PC and safe it again (without changing a thing) to CSV with UTF-8 encoding then it works as expected and loads the correct data. If i open the CSV in Notepad++ i see this difference between manual added items and php added items:
I tried other encoding methods but that is kinda new for me so i think i did something wrong. Thank you in advance for helping me!
I did find the answer after some more debugging. One value of the array didn't contain a string but a true or false statement. This meant that it didn't add the data the right way into the CSV file.
I fixed it by adding strval() to every variable before putting it into an array.
I have one excel orginal.csv file
ID Name Price
1 Xblue 12
2 Yblue 32
3 Zblue 52
And another copy.csv file
ID Name Price
1 Xblue 89
2 Yblue 43
3 Zblue 45
I want to replace rows from orginal.csv to copy.csv where ID is the same.
Can I do this manually or maybe somehow using PHP?
I search for some options on the internet, but I only found getcsv and readcsv functions that can't help me in this case. Cause this is something like updating CSV file.
It may end up in request timeout in PHP because it requires so many loops to do it. If someone can reduce the time complexity of this program then it will work. if it even works it will take a lot of time to do it.
while(! feof($f_pointer)){ //open old csv to update loop1
$ar=fgetcsv($f_pointer); // getting first row
for($i=0;$i<count($ar);$i++){ //loop2 first row array
$f_pointer2=fopen("new.csv","r"); open new csv to get data
while(! feof($f_pointer2)){ // loop3 to find ID in new csv
$ar2=fgetcsv($f_pointer2); //getting each row in array
for($j=0;$j<count($ar2);$j++){ //loop4 to compare id of old csv to new csv and update data
if($ar[i] == $ar2[j]){
foreach ($ar2 as $fields) { //loop5
fputcsv($f_pointer, $fields);
}
}
}
}
}
}
?>
I've created a little soulution. If order is important you don't have to index the array and loop through the copied array.
<?php
if(file_exists('output.csv'))
{
unlink('output.csv');
}
function fputcsv_eol($handle, $array, $delimiter = ',', $enclosure = '"', $eol = "\n") {
$return = fputcsv($handle, $array, $delimiter, $enclosure);
if($return !== FALSE && "\n" != $eol && 0 === fseek($handle, -1, SEEK_CUR)) {
fwrite($handle, $eol);
}
return $return;
}
function scanFile($sFilename, $iIndexColumn)
{
$rFile = fopen($sFilename, 'r');
$aData = array();
while(($aLine = fgetcsv($rFile)) !== false)
{
$aData[$aLine[$iIndexColumn]] = $aLine;
}
fclose($rFile);
return $aData;
}
$iIndexColumn = 0;
$iValueColum = 2;
$aOriginalData = scanFile('original.csv', 0);
$aCopyData = scanFile('copy.csv', 0);
foreach($aOriginalData as $iID => $aOriginalDatum)
{
if(array_key_exists($iID, $aCopyData))
{
$aCopyData[$iID] = $aOriginalDatum;
}
}
$rFile = fopen('output.csv', 'w');
foreach($aCopyData as $aCopyDatum)
{
fputcsv_eol($rFile, $aCopyDatum, ',', '"',"\r\n");
}
fclose($rFile);
I am trying to turn an input file in the form below into a series of objects that can be manipulated.
arabian_sea_area = {
1926 1927 1931 1932 1933 2029 2030
}
gulf_of_aden_sea_area = {
1925 2024 5285 5286
}
sdf
<?php
$all_areas = array();
if (($handle = fopen("area.txt", "r")) == False)
{
die("failed to open file\n");
}
while (($line = fgets($handle)) !== FALSE)
{
if (ctype_alpha($line[0]))
{
$line= explode(" ",$line);
// echo($line[0]."\n");
$area = $line[0];
$IDs = explode(" ", fgets($handle));
$IDs[0] = ltrim($IDs[0], ' '); // trying to remove tab from first ID
$all_areas[$area] = $IDs;
//array_push($all_areas, $temp);
}
}
//echo("a\n");
print_r($all_areas["arabian_sea_area"]);
//var_dump ($all_areas);
?>
The values print correctly in the commented out debug lines but fail to print anything for the var_dump at the end.
edit: I realize this was unclear, what I was trying to do was create a master "all_areas" array that linked to objects titled the first line (ie. arabian_sea_area etc.) and I could then get at the numerical Ids for each area algorithmically for a later script.
There are many issues with your code:
1-
if (ctype_alpha($line[0]))
{
$line= explode(" ",$line);
//echo($line[0]."\n");
$temp = $line[0];
$temp = new Area;
$temp->filler($line, $handle);
}
you are creating a $temp variable but you forgot to push it to your main array $all_areas. use array_push
2-
var_dump ($arabian_sea_area);
$arabian_sea_area does not exist.
Did you mean to print your main array $all_areas ?
3- Recommendation:
On errors (echo("failed to open file\n");) its recommended to use die("failed to open file\n"); instead of echo. as die will stop the rest of the script from executing.
-- UPDATE --
I edited your code in a way that should work fine:
class Area {
public $area_name;
public $IDs = array();
public function filler($line, $handle) {
$this->area_name = $line[0];
//echo($this->area_name."\n");
$this->IDs = explode(" ", fgets($handle));
//print_r($this->IDs);
}
}
$all_areas = array();
if (($handle = fopen("area.txt", "r")) == False)
{
die("failed to open file\n");
}
while (($line = fgets($handle)) !== FALSE)
{
if (ctype_alpha($line[0]))
{
$line= explode(" ",$line);
// echo($line[0]."\n");
$temp = $line[0];
$temp = new Area;
$temp->filler($line, $handle);
array_push($all_areas, $temp);
}
}
//echo("a\n");
var_dump ($all_areas);
You might wanna update it to remove / filter empty values.
Why do I always get a array with one item of empty string for an empty .csv file?
$content = file('products.csv');
print_r($content);
result:
Array ( [0] => )
Can I return false so that I know there is nothing in the csv file?
This seems like ad-hoc behaviour particular to your taste (no problem with that at all). Which means, you should probably create a wrapper function for this.
function contentIsNotEmpty($content) {
$isEmpty = empty($content) || (count($content) == 1 && empty($content[0]));
return $isEmpty ? false : $content;
}
EDIT: Incorporated #scrowler's feedback, and Michael J. Mulligan's.
A single line test should get you the result:
$empty = empty($content) || (count($content) === 1 && empty($content[0]));
The following should avoid the "fake empty":
$empty = empty($content) || (count($content) === 1 && $content[0] === '');
If you need a re-usable function, and prefer, as you stated, to get an array or nothing, this may be helpful:
function get_file($file_name = null, $strict = false) {
$return = null;
if(!empty($file_name) && is_readable($file_name)) {
$contents = file($file_name);
if(
!empty($contents)
&& (
count($contents) > 1
|| (
!empty($contents[0])
|| ($strict && $contents[0] !== '')
)
)
) {
$return = $contents;
}
}
return $return;
}
I mean, we could get all kinds of creative and iterate over lines, etc. But I think you get the idea.
If you want to get a CSV file, I would suggest using a method like fgetcsv() (repurposed):
function getcsv($file_name, $headers = array(), $delimiter = ',', $enclosure = '"', $escape = "\\" ) {
$contents = array();
$get_headers = $headers === FALSE;
$headers = is_array($headers) ? array_values($headers) : array();
if(!empty($file_name) && is_readable($file_name)) {
$row = 0;
if (($handle = fopen($file_name, "r")) !== FALSE) {
while (($data = fgetcsv($handle, 0, $delimiter, $enclosure, $escape)) !== FALSE) {
if($get_headers && empty($headers)) {
$headers = $data;
continue;
}
foreach($data as $i => $col_value) {
$col_name = isset($headers[$i]) ? $headers[$i] : $i;
$contents[$row][$col_name] = $col_value;
}
$row++;
}
fclose($handle);
}
}
return $contents;
}
Note, above is not tested, just a quick draft, and I am going to bed. I'll edit it tomorrow if need be.
Finally, if you are getting a single line, with white-space, and this validates as "empty" in your eyes, simple test it after a trim:
$empty_line = trim($content[$line_num]) == '';
Not sure what else to tell you. I think we have equipped you with quite a few tools and ways to validate this situation. Best of luck.
try this
$content = file('products.csv');
if(!empty($content)){
print_r();}{
else{
// Do something if no content
}
Can i parse a plist file with php and kind of get it into an array, like the $_POST[''] so i could call $_POST['body'] and get the string that has the <key> body ?
CFPropertyList - A PHP Implementation Of Apple's plist (PropertyList)
Googling for "php plist parser" turned up this blog post that seems to be able to do what you are asking for.
Took a look at some of the libraries out there but they have external requirements and seem overkill. Here's a function that simply puts the data in to associative arrays. This worked on a couple of exported itunes plist files I tried.
// pass in the full plist file contents
function parse_plist($plist) {
$result = false;
$depth = [];
$key = false;
$lines = explode("\n", $plist);
foreach ($lines as $line) {
$line = trim($line);
if ($line) {
if ($line == '<dict>') {
if ($result) {
if ($key) {
// adding a new dictionary, the line above this one should've had the key
$depth[count($depth) - 1][$key] = [];
$depth[] =& $depth[count($depth) - 1][$key];
$key = false;
} else {
// adding a dictionary to an array
$depth[] = [];
}
} else {
// starting the first dictionary which doesn't have a key
$result = [];
$depth[] =& $result;
}
} else if ($line == '</dict>' || $line == '</array>') {
array_pop($depth);
} else if ($line == '<array>') {
$depth[] = [];
} else if (preg_match('/^\<key\>(.+)\<\/key\>\<.+\>(.+)\<\/.+\>$/', $line, $matches)) {
// <key>Major Version</key><integer>1</integer>
$depth[count($depth) - 1][$matches[1]] = $matches[2];
} else if (preg_match('/^\<key\>(.+)\<\/key\>\<(true|false)\/\>$/', $line, $matches)) {
// <key>Show Content Ratings</key><true/>
$depth[count($depth) - 1][$matches[1]] = ($matches[2] == 'true' ? 1 : 0);
} else if (preg_match('/^\<key\>(.+)\<\/key\>$/', $line, $matches)) {
// <key>1917</key>
$key = $matches[1];
}
}
}
return $result;
}