define('BS_INIHANDLER_VERSION', '4.0.$x$');
define('BS_INIHANDLER_UNQUOTE_NONE', 0);
define('BS_INIHANDLER_UNQUOTE_DOUBLE', 1);
define('BS_INIHANDLER_UNQUOTE_SINGLE', 2);
define('BS_INIHANDLER_UNQUOTE_ALL', 3);
class Bs_IniHandler extends Bs_Object {
/**
* specifies which chars at the start of a line define a comment line.
* the line is left-trimmed before the comparison is made.
*
* default: '#', '/', ';'
*
* note: you can only specify chars, not strings. so if you want '//' as
* comment char, you need to define '/'. (which is done by default)
*
* @access public
* @var array $commentChars (vector)
*/
var $commentChars = array('#', '/', ';');
/**
* should quoted values be unquoted?
*
* 0 = no
* BS_INIHANDLER_UNQUOTE_SINGLE = only single-quotes 'like this'
* BS_INIHANDLER_UNQUOTE_DOUBLE = only double-quotes "like this"
* BS_INIHANDLER_UNQUOTE_ALL = single and double quotes (default)
*
* @access public
* @var int $unQuote
*/
var $unQuote = BS_INIHANDLER_UNQUOTE_ALL;
/**
* vector with the sections as strings.
*
* note: since this var is not really needed, and all information is available in
* $this->_params, this internal var may disappear in the future.
*
* @access private
* @var array $_sections
*/
var $_sections;
/**
* 2-dim hash where the first dim is a hash with the section names, the 2nd
* is the key/value pair hash.
* @access private
* @var array $_params
*/
var $_params;
/**
* @todo make private
*/
var $comments;
/**
* the fullpath to the currently used file.
* @var string $_fileFullPath
*/
var $_fileFullPath;
/**
* the last error message of the error that occured. not set = no error.
* @access private
* @var string $_lastError
*/
var $_lastError;
/**
* Constructor.
* WARNING: please do not use the param $fileFullPath here, better call loadFile() yourself
* because otherwise you won't know if it worked or not.
* @param string $fileFullPath
*/
function Bs_IniHandler($fileFullPath='') {
parent::Bs_Object(__FILE__); //call parent constructor.
if (!empty($fileFullPath)) {
$this->loadFile($fileFullPath);
}
}
/**
* Loads the given file (read in and parse).
* @access public
* @param string $fileFullPath (a fullpath to the desired file.)
* @return bool (see getLastError())
*/
function loadFile($fileFullPath) {
$this->reset();
if (!file_exists($fileFullPath)) {
$this->_lastError = "File doesn't exists: '{$fileFullPath}'";
return FALSE;
}
if (!is_readable($fileFullPath)) {
$this->_lastError = "File is not readable: '{$fileFullPath}'";
return FALSE;
}
$this->_fileFullPath = $fileFullPath;
$fileContent = file($fileFullPath);
$this->_parseFromArray($fileContent);
return TRUE;
}
/**
* loads the ini stuff from the given string instead of a file (read in and parse).
* @access public
* @param string $str
* @return bool (see getLastError())
*/
function loadString($str) {
$this->reset();
$arr = explode("\n", $str);
$this->_parseFromArray($arr);
return TRUE;
}
/**
* sets the quote handling.
* @access public
* @param int $mode (see constants)
* @return void
*/
function setQuoteHandling($mode=BS_INIHANDLER_UNQUOTE_ALL) {
$this->unQuote = $mode;
}
/**
* gets called from loadFile() and loadString() to parse the data.
* @access private
* @param array (vector filled with strings (lines))
* @return void
*/
function _parseFromArray($arr) {
$this->comments = array();
$comment = array();
$section = '';
foreach($arr as $line) {
$sectionFound = $valueFound = FALSE;
$param = array('key'=>'', 'val'=>'');
do { // try
$line = trim($line);
# Skip empty lines
if (empty($line)) break; // try
# Comment
if (in_array($line[0], $this->commentChars)) {
$comment[] = $line;
break; // try
}
# Section
if (preg_match('/\[(.*)\]/', $line, $ar)) {
$section = $ar[1];
$sectionFound = TRUE;
break; // try
}
# Parameter
// split 1x at first '='
$tmp = explode('=', $line);
if (!is_array($tmp)) break; // try
if (sizeOf($tmp) < 2) {
//invalid comment line, whatever.
//no good if we arrive here. that's some crappy line that should not be in the file.
//we could issue a warning here.
$comment[] = @$tmp[0];
break;
}
$param['key'] = trim($tmp[0]);
array_shift($tmp);
if (sizeOf($tmp)>1) $tmp[0] = implode('=', $tmp);
$param['val'] = isSet($tmp[0]) ? trim($tmp[0]) : '';
if (empty($param['val'])) {
$valueFound = TRUE;
break; // try
}
$unQuote = '';
if ($this->unQuote & BS_INIHANDLER_UNQUOTE_DOUBLE) $unQuote .= '"';
if ($this->unQuote & BS_INIHANDLER_UNQUOTE_SINGLE) $unQuote .= "'";
if (empty($unQuote)) {
$valueFound = TRUE;
break; // try
}
// trim quote
$regEx = '/^(['.$unQuote.']?)(.*)\1$/';
if (preg_match($regEx, $param['val'], $ar)) {
$param['val'] = $ar[2];
$valueFound = TRUE;
break; // try
} else {
//the value had unmatching quotes, like "here' or 'here"
break; // try
}
} while(FALSE);
if ($sectionFound) {
$this->_sections[] = $section;
if (!empty($comment)) $this->comments[$section] = $comment;
$comment = array();
} else if ($valueFound) {
$this->_params[$section][$param['key']] = $param['val'];
if (!empty($comment)) $this->comments[$section .'__'. $param['key']] = $comment;
$comment = array();
}
} // foreach
if (!empty($comment)) $this->comments['__LastComment__'] = $comment;
}
/**
*
*/
function toString() {
$outStr = "# Bs_IniHandler";
foreach ($this->_params as $section => $params) {
if (isSet($this->comments[$section])) {
foreach ($this->comments[$section] as $comment) $outStr .= "{$comment}\n";
}
$outStr .= "[".$section."]\n";
foreach ($params as $key => $value) {
if (isSet($this->comments[$section .'__'. $key])) {
foreach ($this->comments[$section .'__'. $key] as $comment) $outStr .= " {$comment}\n";
}
$outStr .= " " .$key. " = " .$value. "\n";
}
$outStr .= "\n";
}
if (isSet($this->comments['__LastComment__'])) {
foreach ($this->comments['__LastComment__'] as $comment) $outStr .= "{$comment}\n";
}
return $outStr;
}
/**
* saves the ini settings to the file specified.
* @access public
* @param string $fileFullPath (see above)
* @return bool (see getLastError())
* @see saveString()
*/
function saveFile($fileFullPath) {
$outStr = $this->toString();
if (!$fp = fopen($this->_fileFullPath, 'wb')) {
$this->_lastError = "Failed open the file for writing: '{$fileFullPath}'";
return FALSE;
}
if (!fwrite($fp, $outStr)){
$this->_lastError = "Failed to write (but was able to open) the file: '{$fileFullPath}'";
return FALSE;
}
@fclose($fp);
return TRUE;
}
/**
* resets this object so we can re-use it for something else.
* some setting vars are not reset.
*
* resets:
* _sections
* _params
* _fileFullPath
* _lastError
*
* keeps:
* commentChars
* unQuote
*
* @access public
* @return void
*/
function reset() {
unset($this->_sections);
unset($this->_params);
unset($this->_fileFullPath);
unset($this->_lastError);
}
/**
* returns [all parameters|parameter] [for the given section].
*
* examples:
* get() => returns all sections with all params as 2-D hash.
* array of [][] =>
* get('section') => returns all params for the section specified as 1-D hash.
* array of [] =>
* get('section', 'key') => returns the param specified of the section specified as string.
*
* note: if a param is defined in the 'global scope', use an empty string for the
* $section name. example: get('', 'key')
*
* @access public
* @param string $section if not given returns all sections
* @param string $key if not given returns all keys
* @return mixed (see above)
* @throws null (if the given section or key does not exist)
*/
function get($section=NULL, $key=NULL) {
if (is_null($section)) return $this->_params;
if (!isSet($this->_params[$section])) return NULL; //throw
if (is_null($key)) return $this->_params[$section];
if (!isSet($this->_params[$section][$key])) return NULL; //throw
return $this->_params[$section][$key];
}
/**
* tells if the section or key specified is set.
*
* examples:
* has('mySection') => tells if 'mySection' is set
* has('mySection', 'myKey' => tells if myKey in mySection is set.
*
* note: case matters!
*
* @access public
* @param string $section
* @param string $key (default is NULL)
* @return bool
*/
function has($section, $key=NULL) {
if (is_null($key)) {
return (isSet($this->_params[$section])); //using _params instead of _sections cause it's a hash. in_array is slower.
} else {
return (isSet($this->_params[$section]) && isSet($this->_params[$section][$key]));
}
}
/**
* returns the text message of the last error that occured.
*
* call this function if something failed, for example after getting
* bool FALSE back from loadFile().
*
* @access public
* @return mixed (string last error, or NULL if no error occured.)
*/
function getLastError() {
if (is_null($this->_lastError)) return NULL;
return $this->_lastError;
}
}
# SELF - Test
if (basename($_SERVER['PHP_SELF']) == 'Bs_IniHandler.class.php') {
$testData =<< # Comment 1
# comment 2
[]
globalData = foo
# comment A
[Some test data]
# comment B
one = hallo
two = "hallo"
# comment C
food = "Tom's Pizza = 'good stuff'"
more food = Sam's Pizza's = 'best stuff'
empty = ""
noVal =
# comment D
[more test data]
one = hi
two = 'hi'
food = 'Pizza = "good"'
empty = ''
noVal
# comment E
EOD;
$iniHandler = new Bs_IniHandler();
$iniHandler->loadString($testData);
#XR_dump($iniHandler->comments, __LINE__, '', __FILE__);
XR_dump($iniHandler->toString(), __LINE__, '', __FILE__);
}
?>