php - How to parse formatted text that resembles an .ini file?
I have an input form in HTML, which is used to import users data into a database (MYSQL) using PHP.
The information in the input must be like this:
[account]
user = {user1}
pwd = {password1}
expdate = 2028-01-01
[account]
user = {user2}
pwd = {password2}
expdate = 2028-01-01
[account]
user = {user3}
pwd = {password3}
expdate = 2028-01-01
Now, the PHP code should be parsing the number of users and each user info and save each user info separately.
My PHP code is:
$users = $_POST["users"];
$users = explode(chr(10) . chr(10), $users);
for ($i = 0; $i < count($users); $i++) {
$users[$i] = explode(" ", $users[$i]);
$username = $users[$i][2];
$pwd = $users[$i][5];
$exp = $users[$i][8];
if (strtotime($exp) === false) {
// ...code
} else {
$expiredate = date("Y-m-d", strtotime($exp));
}
}
When the input is a single user:
[account]
user = {user1}
pwd = {password1}
expdate = 2028-01-01
My output is mangled (here is the var_export()
output):
array (
'username' => '{user1}
pwd',
'pwd' => '=',
'exp' => '2028-01-01'
)
The problem is the exploding -- the pwd
key is attached to the username
value, the pwd
value =
, and it sometimes give a wrong format for the date which prevents being saved.
As a workaround, after putting spaces after each of the input username and password, all data are parsed successfully.
Also, when I enter multiple users at the input form:
[account]
user = {user1}
pwd = {password1}
expdate = 2028-01-01
[account]
user = {user2}
pwd = {password2}
expdate = 2028-01-01
The PHP code only reads the first user and save the information only for the first one, I want them both being saved.
So:
- I want the PHP code to parse all users information to be saved, not only the first user.
- I want the user information get saved without putting space at the end of the user and password lines.
Tried to edit my code and search a lot, was unlucky , don't know what is wrong with the PHP code.
Answer
Solution:
Your input looks like an .ini
file.
Use to split the input string into pieces separated by
[account]
then use to parse each piece and get the data in an array:
$users = <<< END
[account]
user = {user1}
pwd = {password1}
expdate = 2028-01-01
[account]
user = {user2}
pwd = {password2}
expdate = 2028-01-01
[account]
user = {user3}
pwd = {password3}
expdate = 2028-01-01
END;
// Use $users = $_POST['users'] in the real code
// Parse the input data
$data = array_map(
function($entry) { return parse_ini_string($entry); },
array_filter( // remove the empty entries (before the first '[account]', between two consecutive appearances of '[account]'
explode('[account]', $users),
function($entry) { return strlen(trim($entry)); }
)
);
print_r($data);
The output is:
Array
(
[1] => Array
(
[user] => {user1}
[pwd] => {password1}
[expdate] => 2028-01-01
)
[2] => Array
(
[user] => {user2}
[pwd] => {password2}
[expdate] => 2028-01-01
)
[3] => Array
(
[user] => {user3}
[pwd] => {password3}
[expdate] => 2028-01-01
)
)
Next, processing the parsed information is straight forward:
foreach ($data as $account) {
// do something with $account['user'], $account['pwd'], $account['expdate']
// Use the DateTime class and its friends to validate and handle $account['expdate']
}
parse_ini_string()
is able to parse the entire string in one call and handle the sections correctly if its second argument is true
. However, because all the sections are named account
it merges the values; the key-value pairs that occur later in the input override the previous values.
This is the reason why we first split the input into sections then feed to parse_ini_string()
one section at a time.
Answer
Solution:
You can try like this
PHP - HTML :
<div class="all_forms">
<?php for ($z = 0; $z < 5; $z++) { ?>
<form method="POST" id="passenger-<?= $z ?>-details">
<input type="text" class="form-control" name="username" placeholder="Username">
<input type="password" class="form-control" name="password" placeholder="Password">
<input type="date" class="form-control" name="date" placeholder="date">
</form>
<?php } ?>
</div>
JavaScript - JQuery
var passenger_details_array = [];
$('.all_forms').find("form").map(function(idx, form) {
var str = $('#' + form.id).serialize();
passenger_details_array.push(str);
});
var data = {
token: 'SOME_SECURITY_TOKEN',
passenger_details: passenger_details_array
};
$.post('http://domain/path/file.php', data, function(response) {
console.log(response);
});
PHP - Saving Info
$passenger_details = $_POST['passenger_details'];
foreach ($passenger_details as $value) {
parse_str($value, $tmp);
//here you have $tmp and in this you will find all proper details like (one by one)
// ['username' => 'U1', 'password' => 'user1_pass', 'date' => 'whatever']
}
//$passenger_details has data like
[
['username' => 'U1', 'password' => 'user1_pass', 'date' => 'whatever'],
['username' => 'U2', 'password' => 'user2_pass', 'date' => 'whatever'],
['username' => 'U3', 'password' => 'user3_pass', 'date' => 'whatever']
]
Answer
Solution:
Split on [account]
lines with optional leading newline character sequences and mandatory trailing newline character sequences. Do not allow empty elements to be generated in the explosion.
\R
matches platform-agnostic newline sequences so that do have to worry about \r\n
versus \n
issues.
The ^
(start of string anchor) is modified by the m
pattern modifier so that it matches the start of each line.
?
means zero or one of whatever was before it. This makes the trailing \R
optional.
If your real text has an empty line between user details, you can just add {2}
after the leading \R
to consume the extra newline sequence.
Call parse_ini_string()
on each generated element. Clean and done; no extra filtering or trimming required. No more trouble with the exploding process.
Code: (Demo)
$data = <<<NOT_INI
[account]
user = {user1}
pwd = {password1}
expdate = 2028-01-01
[account]
user = {user2}
pwd = {password2}
expdate = 2028-01-01
[account]
user = {user3}
pwd = {password3}
expdate = 2028-01-01
NOT_INI;
var_export(
array_map(
'parse_ini_string',
preg_split(
'~\R?^\[account]\R~m',
$data,
0,
PREG_SPLIT_NO_EMPTY
)
)
);
Output:
array (
0 =>
array (
'user' => '{user1}',
'pwd' => '{password1}',
'expdate' => '2028-01-01',
),
1 =>
array (
'user' => '{user2}',
'pwd' => '{password2}',
'expdate' => '2028-01-01',
),
2 =>
array (
'user' => '{user3}',
'pwd' => '{password3}',
'expdate' => '2028-01-01',
),
)
Source