In my app I have a textarea, which my users are meant to enter data in the format:
Forename, Surname, YYYY-MM-DD, Company
Forename, Surname, YYYY-MM-DD, Company
on each line. My intention is to then loop through each row, exploding at the comma and trimming any white space.
I then need to pass the exploded array in to an associative array. I'm doing this manually at the moment, on the assumption that the user has entered the data in the correct order and format; which does work, but does rely on the user not messing things up.
What would you suggest as being a better way of doing this? I think the way I'm checking each index to see if it's empty or not seems rather clunky, as well as error prone.
Any suggestions or things to consider?
/************************************
* sample data from textarea:
* Name, Surname, 1980-02-22, Company
* Foo, Bar, 1970-05-12, Baz
************************************/
$data = preg_split('/\r\n|\n/', $_POST['data'],
-1, PREG_SPLIT_NO_EMPTY);
$item = array();
// loop through the data
foreach($data as $row) :
// trim and explode each line in to an array
$item[] = array_map('trim', explode(',', $row));
endforeach;
$k=0;
foreach($item as $user) :
$processed_data[$k]['first_name'] = !empty($user[0]) ? $user[0] : NULL;
$processed_data[$k]['last_name'] = !empty($user[1]) ? $user[1] : NULL;
if(!empty($user[2])) :
$dob = strtotime($user[2]);
if($dob) {
$processed_data[$k]['dob'] = $user[2];
} else {
$processed_data[$k]['dob'] = NULL;
}
else:
$processed_data[$k]['dob'] = NULL;
endif;
$processed_data[$k]['company'] = !empty($user[3]) ? $user[3] : NULL;
$k++;
endforeach;
// print_r($processed_data);
you are from the old school :)
Well, as you say above you expect the user to enter the data correctly in the text area. Well if your app is working currently in a robust system don't touch it, but otherwise you should consider to add different parameters in your post request (one for each filed you want to explode)...
you can do like this to solve the problem you have now:
// The algorithm below believe user send data correctly
// Forename, Surname, YYYY-MM-DD, Company
$names = array('first_name', 'last_name', 'dob', 'company_name');
$lines = explode("\n", $_POST['data']);
$result = array();
foreach ($lines as $ line)
{
$exploded_line = explode(",", $line);
$row = array();
foreach ($exploded_line as $key=>$item) { $row[$names[$key]]= trim($item); }
$result[]=$row;
}
// Now in result there is an array like this
// result[0][first_name]
// result[0][last_name]
// result[0][dob]
// result[0][company_name]
// result[1][first_name]
// [ ... ]
You can encapsulate the parsing and the validation into classes of it's own. Additionally you could do the same for the datastructure holding the tabular data.
class TableParser
{
private $string;
public function __construct($string)
{
$this->string = (string) $string;
}
public function parse()
{
$buffer = $this->string;
$rows = explode("\n", $buffer);
$rows = array_map('trim', $rows);
return $this->parseRows($rows);
}
private function parseRows(array $rows)
{
foreach($rows as &$row)
{
$row = $this->parseRow($row);
}
return $rows;
}
private function parseRow($row)
{
$keys = array('forename', 'surname', 'date', 'company');
$keyCount = count($keys)
$row = explode(',', $row, $keyCount);
if (count($row) != $keyCount)
{
throw new InvalidArgumentException('A row must have 4 columns.');
}
$row = array_map('trim', $row);
$row = array_combine($keys, $row);
return $row;
}
}
This parser is still quite rough. You can improve it over time, e.g. providing better error handling, given information which line failed and such. Such a component can then be easier integrated into your normal application flow as you can return that information back to the user so to enable her to make changes to the input.
Additionally you can put apart the validation into a second class and only do the exploding / trimming in the parser, but validation against count, specifying the array keys as well as validating the date format / value in the second class to keep things more apart.
Related
I am facing some problems while converting a set of semicolon delimited strings to json.
The input string:
si;dialed_no;connect_time;duration;region;call_cost
0;918592877727;2015-08-25 18:51:01;21;India(91);0.029
1;918907777727;2015-08-25 19:04:08;220;India(91);0.232
2;918907777727;2015-08-25 19:09:50;40;India(91);0.058
3;918907777727;2015-08-25 19:10:46;69;India(91);0.087
4;919048232151;2015-08-26 13:30:24;19;India(91);0.029
5;919895842822;2015-08-26 14:23:35;423;India(91);0.435
My code:
function my_wrap($val) {
return '{"test":"' . $val. '"}';
}
$parts = explode(';', $string);
$parts = array_map('my_wrap', $parts);
$json = '[' . implode(',', $parts) . ']';
echo $json;
And the output is like:
[{"test":"dialed_no"},{"test":"connect_time"},{"test":"duration"},{"test":"region"},{"test":"call_cost 0"},{"test":"918592877727"},{"test":"2015-08-25 18:51:01"},{"test":"21"},{"test":"India(91)"},{"test":"0.029 1"},{"test":"918907777727"},{"test":"2015-08-25 19:04:08"},{"test":"220"},{"test":"India(91)"},{"test":"0.232 2"},{"test":"918907777727"},{"test":"2015-08-25 19:09:50"},{"test":"40"},{"test":"India(91)"},{"test":"0.058 3"},{"test":"918907777727"},{"test":"2015-08-25 19:10:46"},{"test":"69"},{"test":"India(91)"},{"test":"0.087 4"},{"test":"919048232151"},{"test":"2015-08-26 13:30:24"},{"test":"19"},{"test":"India(91)"},{"test":"0.029 5"},{"test":"919895842822"},{"test":"2015-08-26 14:23:35"},{"test":"423"},{"test":"India(91)"},{"test":"0.435 6"},{"test":"8801711788025"},{"test":"2015-08-30 19:29:48"},{"test":"1"},{"test":"Bangladesh(880)"},{"test":"0.029 7"},{"test":"8801711788025"},{"test":"2015-08-30 19:29:57"},{"test":"2"},{"test":"Bangladesh(880)"},{"test":"0.029 8"},{"test":"8801711788025"},{"test":"2015-08-30 19:30:07"},{"test":"2"},{"test":"Bangladesh(880)"},{"test":"0.029 9"},{"test":"8801711788025"},{"test":"2015-08-30 19:30:17"},{"test":"1"},{"test":"Bangladesh(880)"},{"test":"0.029 10"},{"test":"8801711788025"},{"test":"2015-08-30 21:24:31"},{"test":"88"},{"test":"Bangladesh(880)"},{"test":"0.087 11"},{"test":"8801833316038"},{"test":"2015-08-31 12:06:15"},{"test":"5"},{"test":"Bangladesh(880)"},{"test":"0.029 12"}]
What I want is like:
[{si:"0",dialed_no:"91xxx",connect_time:"2015-08-25 18:51:01"}, {si:"1",dialed_no:"9184sd",connect_time:"2015-08-25 18:51:01"}]
and so on...
Note: I am getting the above input string from a API URL and not from a csv file or something.
Can you try this code and see if it works as you want?
//we split the single lines
$lines = explode("\n", $string);
$linesArray = array();
//we split each line in a set of elements
foreach($lines as $line){
$linesArray[] = explode(";",$line);
}
//we use the first line of data as an array of headers
$headers = $linesArray[0];
//and remove it
unset($linesArray[0]);
$jsonArray = [];
foreach($linesArray as $l=>$ln){
foreach($ln as $k=>$part){
//we re-build an array with the right headers
$jsonArray[$l][$headers[$k]] = $part;
}
}
print json_encode($jsonArray);
What you described as the output you want is not JSON.
Don't invent your own routines when PHP already has perfectly good ones (e.g. for JSON encoding and CSV parsing).
Assuming that the data is starting in a file....
$data=array();
$y=0;
$header=fgtetcsv($file_handle, 0, ';');
while (!feof($file_handle)) {
$row=fgtetcsv($file_handle, 0, ';');
foreach ($row as $x=>$value) {
$data[$y][$header[$x]]=$value;
}
$y++;
}
print json_encode($data);
Of course this will need some tweaks to handle error conditions and possibly for datasets larger than the working memory of php.
<?php
$records = array_map(
function($e) { // 2: to each line/record apply this functions
return str_getcsv($e, ';'); // 3: split the line/record into fields
},
explode( "\n", data() ) // 1: split data into "lines"/records
);
// 4: now $records is an array of records, each being an array of fields
$fields = array_shift($records); // 5: first record contains the field names, remove from array and assign to $fields
$records = array_map(
function($e) use ($fields) { // 7: this function has access to $fields, i.e. the names of the fields
return array_combine($fields, $e); // 8: see http://docs.php.net/array_combine
},
$records // 6: apply the function above to each element, i.e. record, in $records
);
echo json_encode($records);
function data() {
return <<< eot
si;dialed_no;connect_time;duration;region;call_cost
0;918592877727;2015-08-25 18:51:01;21;India(91);0.029
1;918907777727;2015-08-25 19:04:08;220;India(91);0.232
2;918907777727;2015-08-25 19:09:50;40;India(91);0.058
3;918907777727;2015-08-25 19:10:46;69;India(91);0.087
4;919048232151;2015-08-26 13:30:24;19;India(91);0.029
5;919895842822;2015-08-26 14:23:35;423;India(91);0.435
eot;
}
While I appreciate that VolkerK is using a lot of the right functions, I find that functional syntax creates too much bloat and makes the code harder to read. Furthermore, this task can be accomplished in a single loop and therefore it should be.
Code: (Demo)
$input = <<<SSV
si;dialed_no;connect_time;duration;region;call_cost
0;918592877727;2015-08-25 18:51:01;21;India(91);0.029
1;918907777727;2015-08-25 19:04:08;220;India(91);0.232
2;918907777727;2015-08-25 19:09:50;40;India(91);0.058
3;918907777727;2015-08-25 19:10:46;69;India(91);0.087
4;919048232151;2015-08-26 13:30:24;19;India(91);0.029
5;919895842822;2015-08-26 14:23:35;423;India(91);0.435
SSV;
$lines = explode(PHP_EOL, $input);
$header = str_getcsv(array_shift($lines), ';');
foreach ($lines as $line) {
$result[] = array_combine($header, str_getcsv($line, ';'));
}
echo json_encode($result, JSON_PRETTY_PRINT);
A note to the OP, you must not manually craft a json string. Always rely on the accuracy of json_encode() -- it won't fail you.
I have a file called "single.txt". The contents look like:
Array ( [0] => Ada Lovelace,F,96,ISTJ,Linux,24,99
[1] => Adele Goldberg,F,65,ENFJ,Windows,50,70
[2] => Alan Turing,M,41,ESTP,Mac OS X,31,50...
)
First, when a new person signs up, it adds them with them with their info to the end of the .txt file. I want to be able to check whether they've already signed up and I've written the following function:
function returnPerson($content){
global $person_name;
for($i=0 ; $i < count($content); $i++){
if($person_name == array_slice($content,0,0)){
$person = $content[$i];
return $person;
} else continue;
}
}
But that doesn't seem to be working.
How can I compare the first part of the string, i.e. the name part, to the name of the person checking?
Thanks!
Try something like this... you may have to modify it slightly depending on how your text is coming in, but should get you on the right track :)
function returnPerson($content){
global $person_name;
foreach($content as $profile) {
$profile = explode(',', $profile);
if ($person_name == $profile[0]) {
// Person Exists
return $profile;
}
}
return false; // person does not exist
}
You're "slicing" the same array while you're looping it. It looks like you just need a simple strpos():
if(strpos($content[$i], $person . ',') === 0){
return ...
}
Here's another way that doesn't require a for loop:
$names = array_map('reset', array_map('str_getcsv', $content));
if(in_array($person, $names)){
...
}
It works because your data seems to use the CSV format
You can loop over the elements in the array like this:
foreach ($content as $record) {
// $record now contains string "Ada Lovelace,F,96,ISTJ,Linux,24,99"
}
You can extract fields from a comma-separated string by using the explode() function:
$string = "Ada Lovelace,F,96,ISTJ,Linux,24,99";
$fields = explode(',', $string);
// $fields[0] now contains "Ada Lovelace"
// $fields[1] now comtains "F"
// ... etc
Putting those together, you'll get something like:
foreach ($content as $record) {
$fields = explode(',', $record);
if ($fields[0] == $name_to_check) {
// found it
}
}
// didn't find it
TL;DR
I have this data: var_export and print_r.
And I need to narrow it down to: http://pastebin.com/EqwgpgAP ($data['Stock Information:'][0][0]);
How would one achieve it? (dynamically)
I'm working with vTiger 5.4.0 CRM and am looking to implement a function that would return a particular field information based on search criteria.
Well, vTiger is pretty weakly written system, looks and feels old, everything comes out from hundreds of tables with multiple joins (that's actually not that bad) etc., but job is job.
The need arose from getting usageunit picklist from Products module, Stock Information block.
Since there is no such function as getField();, I am looking forward to filter it out from Blocks, that is actually gathering the information about fields also.
getBlocks(); then calls something close to getFields();, that again something close to getValues(); and so on.
So...
$focus = new $currentModule(); // Products
$displayView = getView($focus->mode);
$productsBlocks = getBlocks($currentModule, $displayView, $focus->mode, $focus->column_fields); // in theory, $focus->column_fields should/could be narrowed down to my specific field, but vTiger doesn't work that way
echo "<pre>"; print_r($productsBlocks); echo "</pre>"; // = http://pastebin.com/3iTDUUgw (huge dump)
As you can see, the array under the key [Stock Information:], that actually comes out from translations (yada, yada...), under [0][0] contains information for usageunit.
Now, I was trying to array_filter(); the data out from there, but only thing I've managed to get is $productsBlocks stripped down to only contain [Stock Information:] with all the data:
$getUsageUnit = function($value) use (&$getUsageUnit) {
if(is_array($value)) return array_filter($value, $getUsageUnit);
if($value == 'usageunit') return true;
};
$productsUsageUnit = array_filter($productsBlocks, $getUsageUnit);
echo "<pre>"; print_r($productsUsageUnit); echo "</pre>"; // = http://pastebin.com/LU6VRC4h (not that huge of a dump)
And, the result I'm looking forward to is http://pastebin.com/EqwgpgAP, that I've manually got by print_r($productsUsageUnit['Stock Information:'][0][0]);.
How do I achieve this? (dynamically...)
function helper($data, $query) {
$result = array();
$search = function ($data, &$stack) use(&$search, $query) {
foreach ($data as $entry) {
if (is_array($entry) && $search($entry, $stack) || $entry === $query) {
$stack[] = $entry;
return true;
}
}
return false;
};
foreach ($data as $sub) {
$parentStack = array();
if ($search($sub, $parentStack)) {
$result[] = $parentStack[sizeof($parentStack) - 2];
}
}
return $result;
}
$node = helper($data, 'usageunit');
print_r($node);
I have the following data in a plain text file:
1. Value
Location : Value
Owner: Value
Architect: Value
2. Value
Location : Value
Owner: Value
Architect: Value
... upto 200+ ...
The numbering and the word Value changes for each segment.
Now I need to insert this data in to a MySQL database.
Do you have a suggestion on how can I traverse and scrape it so I can get the value of the text beside the number, and the value of "location", "owner", "architect" ?
Seems hard to do with DOM scraping class since there is no HTML tags present.
If the data is constantly structured, you can use fscanf to scan them from file.
/* Notice the newlines at the end! */
$format = <<<FORMAT
%d. %s
Location : %s
Owner: %s
Arcihtect: %s
FORMAT;
$file = fopen('file.txt', 'r');
while ($data = fscanf($file, $format)) {
list($number, $title, $location, $owner, $architect) = $data;
// Insert the data to database here
}
fclose($file);
More about fscanf in docs.
If every block has the same structure, you could do this with the file() function: http://nl.php.net/manual/en/function.file.php
$data = file('path/to/file.txt');
With this every row is an item in the array, and you could loop through it.
for ($i = 0; $i<count($data); $i+=5){
$valuerow = $data[$i];
$locationrow = $data[$i+1];
$ownerrow = $data[$i+2];
$architectrow = $data[$i+3];
// strip the data you don't want here, and instert it into the database.
}
That will work with a very simple stateful line-oriented parser. Every line you cumulate parsed data into an array(). When something tells you're on a new record, you dump what you parsed and proceed again.
Line-oriented parsers have a great property : they require little memory and what's most important, constant memory. They can proceed with gigabytes of data without any sweat. I'm managing a bunch of production servers and there's nothing worse than those scripts slurping whole files into memory (then stuffing arrays with parsed content which requires more than twice the original file size as memory).
This works and is mostly unbreakable :
<?php
$in_name = 'in.txt';
$in = fopen($in_name, 'r') or die();
function dump_record($r) {
print_r($r);
}
$current = array();
while ($line = fgets($in)) {
/* Skip empty lines (any number of whitespaces is 'empty' */
if (preg_match('/^\s*$/', $line)) continue;
/* Search for '123. <value> ' stanzas */
if (preg_match('/^(\d+)\.\s+(.*)\s*$/', $line, $start)) {
/* If we already parsed a record, this is the time to dump it */
if (!empty($current)) dump_record($current);
/* Let's start the new record */
$current = array( 'id' => $start[1] );
}
else if (preg_match('/^(.*):\s+(.*)\s*/', $line, $keyval)) {
/* Otherwise parse a plain 'key: value' stanza */
$current[ $keyval[1] ] = $keyval[2];
}
else {
error_log("parsing error: '$line'");
}
}
/* Don't forget to dump the last parsed record, situation
* we only detect at EOF (end of file) */
if (!empty($current)) dump_record($current);
fclose($in);
?>
Obvously you'll need something suited to your taste in function dump_record, like printing a correctly formated INSERT SQL statement.
This will give you what you want,
$array = explode("\n\n", $txt);
foreach($array as $key=>$value) {
$id_pattern = '#'.($key+1).'. (.*?)\n#';
preg_match($id_pattern, $value, $id);
$location_pattern = '#Location \: (.*?)\n#';
preg_match($location_pattern, $value, $location);
$owner_pattern = '#Owner\: (.*?)\n#';
preg_match($owner_pattern, $value, $owner);
$architect_pattern = '#Architect\: (.*?)#';
preg_match($architect_pattern, $value, $architect);
$id = $id[1];
$location = $location[1];
$owner = $owner[1];
$architect = $architect[1];
mysql_query("INSERT INTO table (id, location, owner, architect) VALUES ('".$id."', '".$location."', '".$owner."', '".$architect."')");
//Change MYSQL query
}
Agreed with Topener solution, here's an example if each block is 4 lines + blank line:
$data = file('path/to/file.txt');
$id = 0;
$parsedData = array();
foreach ($data as $n => $row) {
if (($n % 5) == 0) $id = (int) $row[0];
else {
$parsedData[$id][$row[0]] = $row[1];
}
}
Structure will be convenient to use, for MySQL or whatelse. I didn't add code to remove the colon from the first segment.
Good luck!
preg_match_all("/(\d+)\.(.*?)\sLocation\s*\:\s*(.*?)\sOwner\s*\:\s*(.*?)\sArchitect\s*\:\s*(.*?)\s?/i",$txt,$m);
$matched = array();
foreach($m[1] as $k => $v) {
$matched[$v] = array(
"location" => trim($m[2][$v]),
"owner" => trim($m[3][$v]),
"architect" => trim($m[4][$v])
);
}
This is probably a simple question, but how do you iterate through an array, doing something to each one, until the last one and do something different?
I have an array of names. I want to output the list of names separated by commas.
Joe, Bob, Foobar
I don't want a comma at the end of the last name in the array, nor if there is only one value in the array (or none!).
Update: I can't use implode() because I have an array of User model objects where I get the name from each object.
$users = array();
$users[] = new User();
foreach ($users as $user) {
echo $user->name;
echo ', ';
}
How can I achieve this and still use these objects?
Update: I was worrying too much about how many lines of code I was putting in my view script, so I decided to create a view helper instead. Here's what I ended up with:
$array = array();
foreach($users as $user) {
$array[] = $user->name;
}
$names = implode(', ', $array);
Use implode:
$names = array('Joe', 'Bob', 'Foobar');
echo implode(', ', $names); # prints: Joe, Bob, Foobar
To clarify, if there is only one object in the array, the ', ' separator will not be used at all, and a string containing the single item would be returned.
EDIT: If you have an array of objects, and you wanted to do it in a way other than a for loop with tests, you could do this:
function get_name($u){ return $u->name; };
echo implode(', ', array_map('get_name', $users) ); # prints: Joe, Bob, Foobar
$array = array('joe', 'bob', 'Foobar');
$comma_separated = join(",", $array);
output: joe,bob,Foobar
Sometimes you might not want to use implode.
The trick then is to use an auxiliary variable to monitor not the last, but the first time through the loop.
vis:
$names = array('Joe', 'Bob', 'Foobar');
$first = true;
$result = '';
foreach ($names as $name)
{
if (!$first)
$result .= ', ';
else
$first = false;
$result .= $name;
}
implode(', ', $array_of_names)
psuedocode....
integer sigh=container.getsize();
sigh--;
integer gosh=0;
foreach element in container
{
if(gosh!=sigh)
dosomething();
else
doLastElementStuff();
gosh++;
}
looking at all the other answers, it seems PHP has gotten a lot more syntactic S since I last wrote anything in it :D
I come accross this a lot building SQL statements etc.
$joiner = " ";
foreach ($things as $thing) {
echo " $joiner $thing \n";
$joiner = ',';
}
FOr some reason its easier to work out the logic if you think of the ",", "AND" or "OR" as an option/attribute that goes before an item. The problem then becomes how to suppress the the "," on the first line.
I personally found the fastest way (if you're into micro optimization) is:
if(isset($names[1])) {
foreach ($names as $name) {
$result .= $name . ', ';
}
$result = substr($result, 0, -2);
} else {
$result = $names[0];
}
isset($names[1]) is the fastest (albeit not so clear) way of checking the length of an array (or string). In this case, checking for at least two elements is performed.
I actually find it easier to create my comma delimited text a little differently. It's a bit more wordy, but it's less function calls.
<?php
$nameText = '';
for ($i = 0; $i < count($nameArray); $i++) {
if ($i === 0) {
$nameText = $nameArray[$i];
} else {
$nameText .= ',' . $nameArray[$i];
}
}
It adds the comma as a prefix to every name except where it's the first element if the array. I have grown fond of using for as opposed to foreach since I have easy access to the current index and therefore adjacent elements of an array. You could use foreach like so:
<?php
$nameText = '';
$nameCounter = 0;
foreach ($nameArray as $thisName) {
if ($nameCounter === 0) {
$nameText = $thisName;
$nameCounter++;
} else {
$nameText .= ',' . $thisName;
}
}