Loose and multiline parse_ini_file function in Php
Some time ago, I was looking for a multiline parse_ini_file function to use in one of my scripts.
I found nothing on the web apart from other people looking for the same thing. So I decided to do it myself...
Compared with the stock parse_ini_file function, what I wanted was a "raw mode" function - i.e. with no interpretation of value content - and the use of sections. The Php function does not enable these modes by default but I often set them because they are necessary. Therefore my function, parseIniFile, uses these modes.
The ini file syntax supported by parseIniFile is :
[Section name]
key = value value value ; comments
"key key" = "value value value"
key = "value value value
value value value"
- Almost any character is accepted as Key (including spaces, "*" etc) or Value - you can even use "=" inside the value if quotes are used to enclose it.
- You have to put section name inside square brakets.
- A multiline value must be inside quotes.
- Comments are properly processed - i.e. a ";" inside a key or a value won't mean the beginning of a comment.
Here is the "magic" function (only 400 bytes):
function parseIniFile($iIniFile) { $aResult = $aMatches = array(); $a = &$aResult; $s = '\s*([[:alnum:]_\- \*]+?)\s*'; preg_match_all('#^\s*((\['.$s.'\])|(("?)'.$s.'\\5\s*=\s*("?)(.*?)\\7))\s*(;[^\n]*?)?$#ms', @file_get_contents($iIniFile), $aMatches, PREG_SET_ORDER); foreach ($aMatches as $aMatch) { if (empty($aMatch[2])) $a [$aMatch[6]] = $aMatch[8]; else $a = &$aResult [$aMatch[3]]; } return $aResult; }
Just include the function parseIniFile in your scripts and use it as a replacement for the standard Php function parse_ini_file.
The code is available here.
Ah, just what I was looking for.
Sit down, excellent!
Tnx a lot!
Comment by Sergio — February 3, 2009 @ 1:46 am
Exactly what I was looking for, thank you very much!
BTW: I added some \,\? etc. to the $s= regex, so the key can hold commas, question marks, dots as well - however, I did not take the time to analyze the regex, just put it somewhere after the alnum part and it worked
Comment by Steve — February 3, 2009 @ 2:50 pm
Oh jesus, YOU are god…. thanx
Comment by Tobi — February 6, 2009 @ 8:24 am
It seems it has a problem to parse the value of key is 0. For example:
aa1 = bbb
aa2 = 0 ; Error
aa3 = “bbb”
Comment by Johnson — February 16, 2009 @ 6:15 am
You are right, Johnson
The PHP function “empty” considers that the string “0″ is empty.
The line “if (!empty($aMatch[8]))”
has to be changed to : “if (empty($aMatch[2]))”
for safer Section detection.
The code above has been updated
Thanks
Comment by admin — February 16, 2009 @ 10:14 am
Good. It works well after your update.
Thanks.
Comment by Johnson — February 18, 2009 @ 7:32 am
How simple and concise! I love it.
I needed it to handle Smarty-style multi-line values, like so:
longtext=”"”
this is a bunch of text
“”"
I thought it would be easy to add an additional processing block in the middle. This is what I added:
|((”?)’.$s.’\\5\s*=\s*(”"”)(.*?)”"”)
between the two existing blocks in the (|) section. I figured it would capture the multi-line values while leaving alone the standard single-quote values. Sadly, I am but a young padawan to your Jedi master regex skills, and my change killed normal single-quote handling. Sigh.
Comment by Michael — March 7, 2009 @ 6:12 pm
Excellent piece of code!
I made some changes to suit my needs, here’s what’s come out of it… it’s slightly verbose, I thought of leaving it as is for anyone to better inspect… it handles multiline the smarty way (with triple quotes), as the code you provided would not work with conditions like the following:
var = “blabla
This cose is gonna “break”
because of the quote on line end”
My version goes single line with both single and double quotes, and implements the multiline like this:
var = “”"
This is a smarty-like multiline variable
“”"
I didn’t implement delimiter escaping, but at this point it would be trivial.
Here it goes:
function parseDataFile($pFile) {
$aResult =
$aMatches = array();
$a = &$aResult;
$s = ‘\s*([[:alnum:]_\- \*]+?)\s*’;
$RX = ”;
$RX .= ‘[\s\n]*’; // Any space and empty lines, and discard them
$RX .= ‘(?:’;
$RX .= ‘(?:\[\s*(?P[^\n]+?)\s*\])’; // if it’s a section, match it as “section”
$RX .= ‘|’; // else…
$RX .= ‘(?:’;
$RX .= ‘(?P”?)(?P[^\n]+?)(?P=vardelim)’; // get the var name into “var” and it’s delim (should there be one) into “vardelim”
$RX .= ‘\s*=\s*’; // skip the equal sign
$RX .= ‘(?:’; // two possible layouts:
$RX .= ‘(?s:”"”\n(?P.*?)\n”"”\n)’; // multiline, delimited by “”", as in PHP ini files
$RX .= ‘|’; // or…
$RX .= ‘((?P["\']?)(?P.*?)(?P=delim))’; // single line, catching the delimiter, which could be ” or ‘
$RX .= ‘)’;
$RX .= ‘)’;
$RX .= ‘)’;
$RX .= ‘\s*(?P;[^\n]*?)?$’; // Any comment afterwards gets fetched as “comment”, just in case
preg_match_all(’#’ . $RX . ‘#m’, @file_get_contents($pFile), $aMatches, PREG_SET_ORDER);
foreach ($aMatches as $aMatch) {
if (empty($aMatch['section'])) $a[ $aMatch['var'] ] = ( !isset($aMatch['val']) ? $aMatch['multiline'] : $aMatch['val'] );
else $a = &$aResult[ $aMatch['section'] ];
}
return $aResult;
}
Any run against test-cases is very welcome, would save me debug time
Cya!
Comment by bitMaster — March 13, 2009 @ 3:47 pm
nice, but whats the licence?
Comment by someone — April 11, 2009 @ 5:13 pm