We would like to be able to parse INI files into an easy to use structure. Ini files are organized into sections and each section contains key-value assignments. Example INI file:
; This is a comment line and will not be ; interpretted. [section1] key=value key2=value2 [section2] key=value
Here are some common rules that apply to INI files:
assignValue to a key keyNameWe will introduce additional rules:
level1/level2/level3. The full key name (absolute key) is actually the section name plus the key name separated by '/' character, e.g. section/key/subkey. NOTE: Please note that the key 'section/key' defined in global section and 'key' defined in a section 'section' are not the same. The absolute key in the first case is '/section/key' whilst in the second case it is 'section/key'.
The following class allows to assign values to hierarchical keys. It provides factory method for creating a new instance and parsing an INI-file from a string.
/** * Ini structure with nested keys support. * * @author Ivan Georgiev */ class ig_util_IniStructure { var $data = array(); var $separator = '/'; /** * Create a new instance and parse an INI file from a string * to initialize the instance. * * @param string $data * @return ig_util_IniStructure */ function & CreateFromString($data) { $ini =& new ig_util_IniStructure(); $lineNo = 0; $currentSection = ''; foreach(explode("\n", $data) as $line) { $lineNo++; $line = trim($line); // Comments if ( $line == '' || $line{0} == ';' ) { continue; } // Sections if ( $line{0} == '[' ) { if (substr($line, -1, 1) != ']') { trigger_error("(line# $lineNo) Invalid section", E_USER_NOTICE); continue; } $currentSection = substr($line, 1, -1); continue; } // Key-value pairs list($key,$value) = @explode('=', $line, 2); if (is_null($value)) { trigger_error("(line# $lineNo) Invlaid key", E_USER_NOTICE); continue; } $ini->set($currentSection . $ini->separator . trim($key), $ini->_parseValue($value)); } return $ini; } /** * Get value associated with a key. * * @param string key * @param mixed default Value to return if key was not found. * @return mixed The value associated with a key or the default value. */ function get($key, $default=null) { $accessor = $this->_makeAccessor($key); $value = eval("return isset($accessor) ? $accessor : \$default;"); return $value; } /** * Associate a value with a key. * * @param string key * @param mixed value * @return void */ function set($key, $value) { $accessor = $this->_makeAccessor($key); eval("$accessor=\$value;"); } /** * Get an array with all keys that are sub-key to a given key. * * @param string $rootKey * @return array */ function keyArray($rootKey=null) { $accessor = $this->_makeAccessor($rootKey); $keys = eval("return isset($accessor) && is_array($accessor) ? " . "array_keys($accessor) : array();"); return $keys; } /** * The same as {@link keyArray} but returns absolute keys (each key includes * the root key). * * @param string $rootKey * @return array */ function absoluteKeyArray($rootKey) { $keys = $this->keyArray($rootKey); $rootPath = is_null($rootKey) ? '' : $rootKey . $this->separator; foreach(array_keys($keys) as $keyIndex) { $keys[$keyIndex] = $rootPath . $keys[$keyIndex]; } return $keys; } function _makeAccessor($key) { $accessor = '$this->data'; if (!is_null($key)) { $accessor .= '[\'' . str_replace($this->separator, '\'][\'', $key) . '\']'; } return $accessor; } /** * Parse value by trimming the value and looking for special values. * @return true */ function _parseValue($value) { $value = trim($value); switch (strtolower($value)) { case 'true': case 'yes': $value = true; break; case 'false': case 'no': $value = false; break; case 'null': $value = null; break; default: // Nothing } return $value; } }
Here are some ideas for future extensions:
We can add array key support. This means that when key ending with [] is met, its value automatically is converted to an array. E.g.
[section] array_key=value0 array_key[]=value1 array_key[]=value2
results in array value for the 'section/array_key' key:
array('value0', 'value1', 'value2');
The first assigned value wasn't an array, but is automatically converted to array.
The following definition will not result in an array value, because the last line overwrites the previous value definition:
[section] array_key=value0 array_key[]=value1 array_key[]=value2 array_key=scalar value
This can be a simple toString() method that returns an INI file.
To keep the implementation simple, this might be implemented in another class. It can use some abstract reader/writer.
For additional features, e.g. value quotation, numeric values etc. check:
See also other formats, like YAML