I need to parse an .ini file into an array, and later change the values of the array and export it to the same .ini file.
I managed to read the file, but didn’t find any simple way to write it back.
Any suggestions?
Sample .ini file:
1 = 0;
2 = 1372240157; // timestamp.
In order to write the .ini file back, you need to create your own function, for PHP offers no functions out of the box other than for reading (which can be found here: http://php.net/manual/pl/function.parse-ini-file.php).
An example of function that might encapsulate a multidimensional array to .ini-syntax compatible string might look like this:
function arr2ini(array $a, array $parent = array())
{
$out = '';
foreach ($a as $k => $v)
{
if (is_array($v))
{
//subsection case
//merge all the sections into one array...
$sec = array_merge((array) $parent, (array) $k);
//add section information to the output
$out .= '[' . join('.', $sec) . ']' . PHP_EOL;
//recursively traverse deeper
$out .= arr2ini($v, $sec);
}
else
{
//plain key->value case
$out .= "$k=$v" . PHP_EOL;
}
}
return $out;
}
You can test it like this:
$x = [
'section1' => [
'key1' => 'value1',
'key2' => 'value2',
'subsection' => [
'subkey' => 'subvalue',
'further' => ['a' => 5],
'further2' => ['b' => -5]]]];
echo arr2ini($x);
(Note that short array syntax is available only since PHP 5.4+.)
Also note that it doesn't preserve the comments that were present in your question. There are no easy ways to remember them, when it is software (as opposed to a human) that updates the file back.
I've made significant changes to the function provided by rr- (many thanks for the kick-start!)
I was unhappy with the way multidimensional properties are handled in that version. I took the example ini file from the php documentation page for parse_ini_file and got a result which included the keys third_section.phpversion and third_section.urls - not what I expected.
I tried using a RecursiveArrayIterator for unlimited nesting, but unfortunately, a header with key-value pairs under it is the maximum limit of recursion that parse_ini_string will process before choking on an error message.
So I started from scratch, added some curveballs as the fourth and last items, and ended up with this:
$test = array(
'first_section' => array(
'one' => 1,
'five' => 5,
'animal' => "Dodo bird",
),
'second_section' => array(
'path' => "/usr/local/bin",
'URL' => "http://www.example.com/username",
),
'third_section' => array(
'phpversion' => array(5.0, 5.1, 5.2, 5.3),
'urls' => array(
'svn' => "http://svn.php.net",
'git' => "http://git.php.net",
),
),
'fourth_section' => array(
7.0, 7.1, 7.2, 7.3,
),
'last_item' => 23,
);
echo '<pre>';
print_r($test);
echo '<hr>';
$ini = build_ini_string($test);
echo $ini;
echo '<hr>';
print_r( parse_ini_string($ini, true) );
function build_ini_string(array $a) {
$out = '';
$sectionless = '';
foreach($a as $rootkey => $rootvalue){
if(is_array($rootvalue)){
// find out if the root-level item is an indexed or associative array
$indexed_root = array_keys($rootvalue) == range(0, count($rootvalue) - 1);
// associative arrays at the root level have a section heading
if(!$indexed_root) $out .= PHP_EOL."[$rootkey]".PHP_EOL;
// loop through items under a section heading
foreach($rootvalue as $key => $value){
if(is_array($value)){
// indexed arrays under a section heading will have their key omitted
$indexed_item = array_keys($value) == range(0, count($value) - 1);
foreach($value as $subkey=>$subvalue){
// omit subkey for indexed arrays
if($indexed_item) $subkey = "";
// add this line under the section heading
$out .= "{$key}[$subkey] = $subvalue" . PHP_EOL;
}
}else{
if($indexed_root){
// root level indexed array becomes sectionless
$sectionless .= "{$rootkey}[] = $value" . PHP_EOL;
}else{
// plain values within root level sections
$out .= "$key = $value" . PHP_EOL;
}
}
}
}else{
// root level sectionless values
$sectionless .= "$rootkey = $rootvalue" . PHP_EOL;
}
}
return $sectionless.$out;
}
My input and output arrays match (functionally, anyway) and my ini file looks like this:
fourth_section[] = 7
fourth_section[] = 7.1
fourth_section[] = 7.2
fourth_section[] = 7.3
last_item = 23
[first_section]
one = 1
five = 5
animal = Dodo bird
[second_section]
path = /usr/local/bin
URL = http://www.example.com/username
[third_section]
phpversion[] = 5
phpversion[] = 5.1
phpversion[] = 5.2
phpversion[] = 5.3
urls[svn] = http://svn.php.net
urls[git] = http://git.php.net
I know it may be a little overkill, but I really needed this function in two of my own projects. Now I can read an ini file, make changes and save it.
The answer by RR works and I added one change
in else statement
//plain key->value case
$out .= "$k=$v" . PHP_EOL;
change it to
//plain key->value case
$out .= "$k=\"$v\"" . PHP_EOL;
By having " around the value, you can have larges values in the INI otherwise parse_ini_* functions will have an issue
http://missioncriticallabs.com/blog/2009/08/double-quotation-marks-in-php-ini-files/
This is my enhanced version answer of rr- (thanks to him), my function is a part of class in laravel eco-system so a function named Arr::isAssoc is used which is basically to detect whether the given array is an associative array or not.
private function arrayToConfig(array $array, array $parent = []): string
{
$returnValue = '';
foreach ($array as $key => $value)
{
if (is_array($value)) // Subsection case
{
// Merge all the sections into one array
if (is_int($key)) $key++;
$subSection = array_merge($parent, (array)$key);
// Add section information to the output
if (Arr::isAssoc($value))
{
if (count($subSection) > 1) $returnValue .= PHP_EOL;
$returnValue .= '[' . implode(':', $subSection) . ']' . PHP_EOL;
}
// Recursively traverse deeper
$returnValue .= $this->arrayToConfig($value, $subSection);
$returnValue .= PHP_EOL;
}
elseif (isset($value)) $returnValue .= "$key=" . (is_bool($value) ? var_export($value, true) : $value) . PHP_EOL; // Plain key->value case
}
return count($parent) ? $returnValue : rtrim($returnValue) . PHP_EOL;
}
What about using php internal functions ? http://php.net/manual/en/function.parse-ini-file.php
Related
Some time ago I had to parse nested data attributes to a JSON, so I found a JS solution here on SO. Eg.:
data-title="Title" data-ajax--url="/ajax/url" data-ajax--timeout="10" data-ajax--params--param-1="Param 1"
to
['title' => 'Title', 'ajax' => ['url' => '/ajax/url', 'timeout' => 10, 'params' => ['param-1' => 'Param 1']]]
So now I need a reverse action in PHP. I need to make attributes string from nested array to use it later in HTML. There can be infinite levels.
I've tried recursive functions. Tried recursive iterators. Still no luck. I always lose top level keys and get something like data-ajax--url=[...] --timeout=[...] --param-1=[...] (missing -ajax part) and so on. The part I can't get is the keys - getting values is easy. Any advice would be welcome.
This can be achieved with some simple concepts like loop,
recursive function and static variable.
Use of static variables is very important here since they remember the last modified value within the function's last call.
Within the loop, we are checking if the currently traversed value is an array.
If it's an array, we are modifying the prefix with the current key and calling the recursive function and .
If not, we are simply concatenating the prefix with the present key.
Try this:
$data = ['title' => 'Title', 'ajax' => ['url' => '/ajax/url', 'timeout' => 10, 'params' => ['param-1' => 'Param 1']]];
function formatter($data = array()) {
static $prefix = 'data-';
static $attr_string = '';
foreach($data as $key => $value) {
if (is_array($value)) {
$prefix .= $key.'--';
formatter($value);
} else {
$attr_string .= $prefix.$key.'="'.$value.'" ';
}
}
return $attr_string;
}
echo formatter($data);
Output:
data-title="Title" data-ajax--url="/ajax/url" data-ajax--timeout="10" data-ajax--params--param-1="Param 1"
So after almost 6 hours of trying to figure this out and lots of searches, also with some hints from Object Manipulator, I found this answer on SO and just had to adapt it to my needs:
function makeDataAttributes(array $attributes)
{
$rs = '';
$iterator = new \RecursiveIteratorIterator(new \RecursiveArrayIterator($attributes));
foreach ($iterator as $key => $value) {
for ($i = $iterator->getDepth() - 1; $i >= 0; $i--) {
$key = $iterator->getSubIterator($i)->key() . '--' . $key;
}
$rs .= ' data-' . $key . '="' . $iterator->current() . '"';
}
return trim($rs);
}
Thanks everyone for your comments. It helped me to define my search more clearly. Also got some new knowledge about iterators.
I wanted to ask, how can i modify array values when doing foreach - in my example, i want to add to every element, but this code produces error.
foreach ($logo as &$value) {
$value = '<div>' . $value . '</div>';
debug ($value);
}
?>
and the error: Notice (8): Array to string conversion
I'm using php 5.6
Thanks for reply.
I use
$serviceMe = ""; //OR use beginning html
foreach( $tmp as $t ) $serviceMe .= '<tr>' . $t . '</tr>';
$serviceMe .= ""; // Rest of the needed html
Make absolutely sure what type of is your $value. Is it integer,float,string,object,array... etc
I think your error comes from trying to convert array to string without proper handling.
$array = new array(
'key' => $value,
'key2' => $value2,
'key3' => $value3,
);
$string = "" . $array; //throws error!
I'm successfully computing tf-idf from an array. Now I want that tf-idf should be computed from multiple text files as I have multiple text files in my directory. Can anyone please modify this code for multiple text files so that first all the files in the directory should read and then on the basis of these files contents tf-idf computed.. Below is my code thanks...
$collection = array(
1 => 'this string is a short string but a good string',
2 => 'this one isn\'t quite like the rest but is here',
3 => 'this is a different short string that\' not as short'
);
$dictionary = array();
$docCount = array();
foreach($collection as $docID => $doc) {
$terms = explode(' ', $doc);
$docCount[$docID] = count($terms);
foreach($terms as $term) {
if(!isset($dictionary[$term])) {
$dictionary[$term] = array('df' => 0, 'postings' => array());
}
if(!isset($dictionary[$term]['postings'][$docID])) {
$dictionary[$term]['df']++;
$dictionary[$term]['postings'][$docID] = array('tf' => 0);
}
$dictionary[$term]['postings'][$docID]['tf']++;
}
}
$temp = ('docCount' => $docCount, 'dictionary' => $dictionary);
Computing tf-idf
$index = $temp;
$docCount = count($index['docCount']);
$entry = $index['dictionary'][$term];
foreach($entry['postings'] as $docID => $postings) {
echo "Document $docID and term $term give TFIDF: " .
($postings['tf'] * log($docCount / $entry['df'], 2));
echo "\n";
}
Have a look at this answer: Reading all file contents from a directory - php
There you find the information how to read all the file contents from a directory.
With this information you should be able to modify your code by yourselve to get it work like expected.
I'm trying to figure out a better/cleaner way to have something like this in PHP:
// This will be parsed as the only argument in a function...
$params = array('Name', 'Age', 'Mail' => 'some#mail.com');
"Name" and "Age" are values with automatic keys (0 and 1, eg.), and "Mail" is a key with "some#mail.com" value:
[0] => 'Name',
[1] => 'Age',
['Mail'] => 'some#mail.com'
When running through it in a foreach loop, to have "Name" and "Age" as the actual parameters I'm using this:
foreach ($params as $k => $i) {
// This is the ugly part!
if (is_int($k)) {
$k = $i;
$i = false;
}
// To do something like this, for example...
$out = "";
if ($i) {
$out .= "<p>$k</p>\n";
} else {
$out .= "<p>$k<p>\n";
}
}
That will return something like this:
<p>Name</p>
<p>Age</p>
<p>Mail</p>
Is there a better way to do it?
Thanks in advance.
Edit #1: Elaborating the question: is there a clean PHP way to distinguish elements that have explicitly informed keys from the ones that have not in the same array?
You are right about the ugly part. It should be better when you use keys for all elements, and decide on the key name whether of not to use the mail link:
$params = array('name'=>'Name', 'age'=>'Age', 'mail'=>'some#mail.com');
foreach ($params as $k => $i) {
$out .= ($k == 'mail') ? '$k' : $k;
}
Why not just give name and age a key?
Or better yet, if you know that the params array will always be in this format, ditch the loop all together:
echo '<p>'.$params[0].'</p>';
echo '<p>'.$params[1].'</p>';
echo '<p>Mail</p>;
if (!is_int($k)) {
$out .= "<p>$k</p>\n";
} else {
$out .= "<p>$i<p>\n";
}
I found 2 solution for what I wanted:
1 - Sticking to the original approach, I could pass the arguments as follows:
my_function(['Name', 'Age', ['Mail' => 'a#mail.com']]);
or...
my_function(array('Name', 'Age', array('Mail' => 'a#mail.com')));
Using "is_array()" later on to distinguish the parameter's type:
if (is_array($i)) {
$p = current($i);
$c = key($i);
$out .= "<p>$c</p>\n";
} else {
$out .= "<p>$i<p>\n";
}
2 - But, changing my original paradigm, I found another option to parse a undefined number of arguments: the function "func_get_args()". My function would be called like this:
my_function('Name', 'Age', ['Mail' => 'a#mail.com']);
or...
my_function('Name', 'Age', array('Mail' => 'a#mail.com'));
Here is an example to create table rows:
function add_table_row() {
$args = func_get_args(); // returns an array of arguments...
$o = "<tr>\n";
foreach ($args as $value) {
if (is_array($value)) { // If the argument is an array...
$cellcont = key($value); // The contents is the key...
$param = current($value); // And the parameter is the value...
$o .= "<td $param>$cellcont</td>\n";
} else {
$o .= "<td>$value</td>\n";
}
}
$o .= "</tr>\n";
return $o;
}
Bottom line, both solutions uses "is_array" the same way, but the second one looks more clear, IMO.
I have CSV file which contains a list of files and directories:
Depth;Directory;
0;bin
1;basename
1;bash
1;cat
1;cgclassify
1;cgcreate
0;etc
1;aliases
1;audit
2;auditd.conf
2;audit.rules
0;home
....
Each line depends on the above one (for the depth param)
I would like to create an array like this one in order to store it into my MongoDB collection with Materialized Paths
$directories = array(
array('_id' => null,
'name' => "auditd.conf",
'path' => "etc,audit,auditd.conf"),
array(....)
);
I don't know how to process...
Any ideas?
Edit 1:
I'm not really working with directories - it's an example, so I cannot use FileSystems functions or FileIterators.
Edit 2:
From this CSV file, I'm able to create a JSON nested array:
function nestedarray($row){
list($id, $depth, $cmd) = $row;
$arr = &$tree_map;
while($depth--) {
end($arr );
$arr = &$arr [key($arr )];
}
$arr [$cmd] = null;
}
But i'm not sure it's the best way to proceed...
This should do the trick, I think (it worked in my test, at least, with your data). Note that this code doesn't do much error checking and expects the input data to be in proper order (i.e. starting with level 0 and no holes).
<?php
$input = explode("\n",file_get_contents($argv[1]));
array_shift($input);
$data = array();
foreach($input as $dir)
{
if(count($parts = str_getcsv($dir, ';')) < 2)
{
continue;
}
if($parts[0] == 0)
{
$last = array('_id' => null,
'name' => $parts[1],
'path' => $parts[1]);
$levels = array($last);
$data[] = $last;
}
else
{
$last = array('id' => null,
'name' => $parts[1],
'path' => $levels[$parts[0] - 1]['path'] . ',' . $parts[1]);
$levels[$parts[0]] = $last;
$data[] = $last;
}
}
print_r($data);
?>
The "best" way to go would be to not store your data in CSV format, as it's the Wrong Tool For The Job.
That said, here you go:
<?php
$lines = file('/path/to/your/csv_file.csv');
$directories = array();
$path = array();
$lastDepth = NULL;
foreach ($lines as $line) {
list($depth, $dir) = str_getcsv($line, ';');
// Skip headers and such
if (!ctype_digit($depth)) {
continue;
}
if ($depth == $lastDepth) {
// If this depth is the same as the last, pop the last directory
// we added off the stack
array_pop($path);
} else if ($depth == 0) {
// At depth 0, reset the path
$path = array();
}
// Push the current directory onto the path stack
$path[] = $dir;
$directories[] = array(
'_id' => NULL,
'name' => $dir,
'path' => implode(',', $path)
);
$lastDepth = $depth;
}
var_dump($directories);
Edit:
For what it's worth, once you have the desired nested structure in PHP, it would probably be a good idea to use json_encode(), serialize(), or some other format to store it to disk again, and get rid of the CSV file. Then you can just use json_decode() or unserialize() to get it back in PHP array format whenever you need it again.