From 903ae8a581fac1e6917fc3e31d2ad8fb91df80c3 Mon Sep 17 00:00:00 2001 From: ctrlaltca <> Date: Thu, 12 Jul 2012 11:21:01 +0000 Subject: standardize the use of unix eol; use svn properties to enforce native eol --- framework/I18N/TChoiceFormat.php | 220 ++-- framework/I18N/TDateFormat.php | 506 ++++----- framework/I18N/TGlobalization.php | 598 +++++------ framework/I18N/TGlobalizationAutoDetect.php | 96 +- framework/I18N/TI18NControl.php | 180 ++-- framework/I18N/TNumberFormat.php | 502 ++++----- framework/I18N/TTranslate.php | 510 ++++----- framework/I18N/TTranslateParameter.php | 236 ++--- framework/I18N/core/ChoiceFormat.php | 452 ++++---- framework/I18N/core/CultureInfo.php | 1264 +++++++++++------------ framework/I18N/core/DateFormat.php | 1304 ++++++++++++------------ framework/I18N/core/DateTimeFormatInfo.php | 1032 +++++++++---------- framework/I18N/core/Gettext/MO.php | 710 ++++++------- framework/I18N/core/Gettext/PO.php | 320 +++--- framework/I18N/core/Gettext/TGettext.php | 572 +++++------ framework/I18N/core/HTTPNegotiator.php | 258 ++--- framework/I18N/core/IMessageSource.php | 244 ++--- framework/I18N/core/MessageCache.php | 342 +++---- framework/I18N/core/MessageFormat.php | 510 ++++----- framework/I18N/core/MessageSource.php | 672 ++++++------ framework/I18N/core/MessageSource_Database.php | 646 ++++++------ framework/I18N/core/MessageSource_MySQL.php | 836 +++++++-------- framework/I18N/core/MessageSource_SQLite.php | 708 ++++++------- framework/I18N/core/MessageSource_XLIFF.php | 1058 +++++++++---------- framework/I18N/core/MessageSource_gettext.php | 914 ++++++++--------- framework/I18N/core/NumberFormat.php | 612 +++++------ framework/I18N/core/NumberFormatInfo.php | 1300 +++++++++++------------ framework/I18N/core/TCache_Lite.php | 1258 +++++++++++------------ framework/I18N/core/util.php | 374 +++---- 29 files changed, 9117 insertions(+), 9117 deletions(-) (limited to 'framework/I18N') diff --git a/framework/I18N/TChoiceFormat.php b/framework/I18N/TChoiceFormat.php index a1d7ad74..401b25d9 100644 --- a/framework/I18N/TChoiceFormat.php +++ b/framework/I18N/TChoiceFormat.php @@ -1,111 +1,111 @@ - - * @link http://www.pradosoft.com/ + + * @link http://www.pradosoft.com/ * @copyright Copyright © 2005-2012 PradoSoft - * @license http://www.pradosoft.com/license/ - * @version $Id$ - * @package System.I18N - */ - - /** - * Get the ChoiceFormat class. - */ -Prado::using('System.I18N.core.ChoiceFormat'); -Prado::using('System.I18N.TTranslate'); - -/** - * TChoiceFormat class. - * - * This component performs message/string choice translation. The translation - * source is set in the TGlobalization module. The following example - * demonstrates a simple 2 choice message translation. - * - * [1] One Apple. |[2] Two Apples - * - * - * The Choice has Value "1" (one), thus the translated string - * is "One Apple". If the Value is "2", then it will show - * "Two Apples". - * - * The message/string choices are separated by the pipe "|" followed - * by a set notation of the form - * # [1,2] -- accepts values between 1 and 2, inclusive. - * # (1,2) -- accepts values between 1 and 2, excluding 1 and 2. - * # {1,2,3,4} -- only values defined in the set are accepted. - * # [-Inf,0) -- accepts value greater or equal to negative infinity - * and strictly less than 0 - * Any non-empty combinations of the delimiters of square and round brackets - * are acceptable. - * - * The string choosen for display depends on the Value property. - * The Value is evaluated for each set until the Value is found - * to belong to a particular set. - * - * Properties - * - Value, float, - *
Gets or sets the Value that determines which string choice to display. - * Since version 3.1.2 the following set notation is also possible. - * - * # {n: n % 10 > 1 && n % 10 < 5} -- matches numbers like 2, 3, 4, 22, 23, 24 - * - * Where set is defined by the expression after n:. In particular, the expression - * accepts the following mathematical/logical operators to form a set of logical conditions - * on the value given by n: - * # < -- less than. - * # <= -- less than equals. - * # > -- greater than. - * # >= -- greater than equals. - * # == -- of equal value. - * # % -- modulo, e.g., 1 % 10 equals 1, 11 % 10 equals 1. - * # - -- minus, negative. - * # + -- addition. - * # & -- conditional AND. - * # && -- condition AND with short circuit. - * # | -- conditional OR. - * # || -- conditional OR with short circuit. - * # ! -- negation. - * - * Additional round brackets can also be used to perform grouping. - * - * @author Xiang Wei Zhuo - * @version v1.0, last update on Fri Dec 24 21:38:49 EST 2004 - * @package System.I18N - */ -class TChoiceFormat extends TTranslate -{ - /** - * @return float the numerical value. - */ - public function getValue() - { - return $this->getViewState('Value',''); - } - - /** - * Sets the numerical choice value - * @param float the choice value - */ - public function setValue($value) - { - $this->setViewState('Value',$value,''); - } - - /** - * Display the choosen translated string. - * Overrides the parent method, also calls parent's renderBody to - * translate. - */ - protected function translateText($text, $subs) - { - $text = parent::translateText($text, $subs); - $choice = new ChoiceFormat(); - $value = $this->getValue(); - $string = $choice->format($text, $value); - if($string) - return strtr($string, array('{Value}'=> $value)); - } -} -?> + * @license http://www.pradosoft.com/license/ + * @version $Id$ + * @package System.I18N + */ + + /** + * Get the ChoiceFormat class. + */ +Prado::using('System.I18N.core.ChoiceFormat'); +Prado::using('System.I18N.TTranslate'); + +/** + * TChoiceFormat class. + * + * This component performs message/string choice translation. The translation + * source is set in the TGlobalization module. The following example + * demonstrates a simple 2 choice message translation. + * + * [1] One Apple. |[2] Two Apples + * + * + * The Choice has Value "1" (one), thus the translated string + * is "One Apple". If the Value is "2", then it will show + * "Two Apples". + * + * The message/string choices are separated by the pipe "|" followed + * by a set notation of the form + * # [1,2] -- accepts values between 1 and 2, inclusive. + * # (1,2) -- accepts values between 1 and 2, excluding 1 and 2. + * # {1,2,3,4} -- only values defined in the set are accepted. + * # [-Inf,0) -- accepts value greater or equal to negative infinity + * and strictly less than 0 + * Any non-empty combinations of the delimiters of square and round brackets + * are acceptable. + * + * The string choosen for display depends on the Value property. + * The Value is evaluated for each set until the Value is found + * to belong to a particular set. + * + * Properties + * - Value, float, + *
Gets or sets the Value that determines which string choice to display. + * Since version 3.1.2 the following set notation is also possible. + * + * # {n: n % 10 > 1 && n % 10 < 5} -- matches numbers like 2, 3, 4, 22, 23, 24 + * + * Where set is defined by the expression after n:. In particular, the expression + * accepts the following mathematical/logical operators to form a set of logical conditions + * on the value given by n: + * # < -- less than. + * # <= -- less than equals. + * # > -- greater than. + * # >= -- greater than equals. + * # == -- of equal value. + * # % -- modulo, e.g., 1 % 10 equals 1, 11 % 10 equals 1. + * # - -- minus, negative. + * # + -- addition. + * # & -- conditional AND. + * # && -- condition AND with short circuit. + * # | -- conditional OR. + * # || -- conditional OR with short circuit. + * # ! -- negation. + * + * Additional round brackets can also be used to perform grouping. + * + * @author Xiang Wei Zhuo + * @version v1.0, last update on Fri Dec 24 21:38:49 EST 2004 + * @package System.I18N + */ +class TChoiceFormat extends TTranslate +{ + /** + * @return float the numerical value. + */ + public function getValue() + { + return $this->getViewState('Value',''); + } + + /** + * Sets the numerical choice value + * @param float the choice value + */ + public function setValue($value) + { + $this->setViewState('Value',$value,''); + } + + /** + * Display the choosen translated string. + * Overrides the parent method, also calls parent's renderBody to + * translate. + */ + protected function translateText($text, $subs) + { + $text = parent::translateText($text, $subs); + $choice = new ChoiceFormat(); + $value = $this->getValue(); + $string = $choice->format($text, $value); + if($string) + return strtr($string, array('{Value}'=> $value)); + } +} +?> diff --git a/framework/I18N/TDateFormat.php b/framework/I18N/TDateFormat.php index 831fe71d..7604dd44 100644 --- a/framework/I18N/TDateFormat.php +++ b/framework/I18N/TDateFormat.php @@ -1,254 +1,254 @@ - - * @link http://www.pradosoft.com/ - * @copyright Copyright © 2005-2012 PradoSoft - * @license http://www.pradosoft.com/license/ - * @version $Id$ - * @package System.I18N - */ - -/** - * Get the DateFormat class. - */ -Prado::using('System.I18N.core.DateFormat'); - -/** - * Get the parent control class. - */ -Prado::using('System.I18N.TI18NControl'); - -/** - * To format dates and/or time according to the current locale use - * - * - * - * The date will be formatted according to the current locale (or culture) - * using the format specified by 'Pattern' attribute. - * - * To format date and/or time for a locale (e.g. de_DE) include a Culture - * attribute, for example: - * - * - * - * The date will be formatted according to this format. - * - * If no Pattern was specified then the date will be formatted with the - * default format (both date and time). If no value for the date is specified - * then the current date will be used. E.g.: - * will result in the current date, formatted with default localized pattern. - * - * Namespace: System.I18N - * - * Properties - * - Value, date, - *
Gets or sets the date to format. The tag content is used as Value - * if the Value property is not specified. - * - Pattern, string, - *
Gets or sets the formatting pattern. The predefined patterns are - * 'fulldate', 'longdate', 'mediumdate', 'shortdate', 'fulltime', - * 'longtime', 'mediumtime', and 'shorttime'. Custom patterns can specified - * when the Pattern property does not match the predefined patterns. - * - DefaultText, string, - *
Gets or sets the default text. If Value is not set, DefaultText will be - * shown instead of todays date and time. - * - * @author Xiang Wei Zhuo - * @version v1.0, last update on Sat Dec 11 15:25:11 EST 2004 - * @package System.I18N - */ -class TDateFormat extends TI18NControl implements IDataRenderer -{ - /** - * Default DateFormat, set to the application culture. - * @var DateFormat - */ - protected static $formatter; - - /** - * A set of pattern presets and their respective formatting shorthand. - * @var array - */ - static private $_patternPresets = array( - 'fulldate'=>'P','full'=>'P', - 'longdate'=>'D','long'=>'d', - 'mediumdate'=>'p','medium'=>'p', - 'shortdate'=>'d','short'=>'d', - 'fulltime'=>'Q', 'longtime'=>'T', - 'mediumtime'=>'q', 'shorttime'=>'t'); - - /** - * Sets the date time formatting pattern. - * @param string format pattern. - */ - public function setPattern($value) - { - $this->setViewState('Pattern',$value,''); - } - - /** - * Gets the date time format pattern. - * @return string format pattern. - */ - public function getPattern() - { - $string = $this->getViewState('Pattern',''); - - $pattern = null; - - //try the subpattern of "date time" presets - $subpatterns = explode(' ',$string,2); - $datetime = array(); - if(count($subpatterns)==2) - { - $datetime[] = $this->getPreset($subpatterns[0]); - $datetime[] = $this->getPreset($subpatterns[1]); - } - - //we have a good subpattern - if(count($datetime) == 2 - && strlen($datetime[0]) == 1 - && strlen($datetime[1]) == 1) - { - $pattern = $datetime; - } - else //no subpattern, try the presets - $pattern = $this->getPreset($string); - - //no presets found, use the string as the pattern - //and let the DateFormat handle it. - if($pattern===null) - $pattern = $string; - if (!is_array($pattern) && strlen($pattern) == 0) - $pattern = null; - return $pattern; - } - - /** - * For a given string, try and find a preset pattern. - * @param string the preset pattern name - * @return string a preset pattern if found, null otherwise. - */ - protected function getPreset($string) - { - $string = strtolower($string); - foreach(self::$_patternPresets as $pattern => $preset) - { - if($string == $pattern) - return $preset; - } - } - - /** - * Get the date-time value for this control. - * @return string date time value. - */ - public function getValue() - { - $value = $this->getViewState('Value',''); - if(empty($value)) - { - $defaultText = $this->getDefaultText(); - if(empty($defaultText)) - return time(); - } - return $value; - } - - /** - * Set the date-time value for this control. - * @param string the date-time value. - */ - public function setValue($value) - { - $this->setViewState('Value',$value,''); - } - - /** - * Get the default text value for this control. - * @return string default text value - */ - public function getDefaultText() - { - return $this->getViewState('DefaultText',''); - } - - /** - * Set the default text value for this control. - * @param string default text value - */ - public function setDefaultText($value) - { - $this->setViewState('DefaultText',$value,''); - } - - /** - * Get the date-time value for this control. - * This method is required by {@link IDataRenderer}. - * It is the same as {@link getValue()}. - * @return string date time value. - * @see getValue - * @since 3.1.2 - */ - public function getData() - { - return $this->getValue(); - } - - /** - * Set the date-time value for this control. - * This method is required by {@link IDataRenderer}. - * It is the same as {@link setValue()}. - * @param string the date-time value. - * @see setValue - * @since 3.1.2 - */ - public function setData($value) - { - $this->setValue($value); - } - - /** - * Renders the localized version of the date-time value. - * If the culture is not specified, the default application - * culture will be used. - * This method overrides parent's implementation. - */ - protected function getFormattedDate() - { - $value = $this->getValue(); - $defaultText = $this->getDefaultText(); - if(empty($value) && !empty($defaultText)) - return $this->getDefaultText(); - - $app = $this->getApplication()->getGlobalization(); - - //initialized the default class wide formatter - if(self::$formatter===null) - self::$formatter = new DateFormat($app->getCulture()); - - $culture = $this->getCulture(); - - //return the specific cultural formatted date time - if(strlen($culture) && $app->getCulture() !== $culture) - { - $formatter = new DateFormat($culture); - return $formatter->format($value, - $this->getPattern(), - $this->getCharset()); - } - //return the application wide culture formatted date time. - $result = self::$formatter->format($value, - $this->getPattern(), - $this->getCharset()); - return $result; - } - - public function render($writer) - { - $writer->write($this->getFormattedDate()); - } - + + * @link http://www.pradosoft.com/ + * @copyright Copyright © 2005-2012 PradoSoft + * @license http://www.pradosoft.com/license/ + * @version $Id$ + * @package System.I18N + */ + +/** + * Get the DateFormat class. + */ +Prado::using('System.I18N.core.DateFormat'); + +/** + * Get the parent control class. + */ +Prado::using('System.I18N.TI18NControl'); + +/** + * To format dates and/or time according to the current locale use + * + * + * + * The date will be formatted according to the current locale (or culture) + * using the format specified by 'Pattern' attribute. + * + * To format date and/or time for a locale (e.g. de_DE) include a Culture + * attribute, for example: + * + * + * + * The date will be formatted according to this format. + * + * If no Pattern was specified then the date will be formatted with the + * default format (both date and time). If no value for the date is specified + * then the current date will be used. E.g.: + * will result in the current date, formatted with default localized pattern. + * + * Namespace: System.I18N + * + * Properties + * - Value, date, + *
Gets or sets the date to format. The tag content is used as Value + * if the Value property is not specified. + * - Pattern, string, + *
Gets or sets the formatting pattern. The predefined patterns are + * 'fulldate', 'longdate', 'mediumdate', 'shortdate', 'fulltime', + * 'longtime', 'mediumtime', and 'shorttime'. Custom patterns can specified + * when the Pattern property does not match the predefined patterns. + * - DefaultText, string, + *
Gets or sets the default text. If Value is not set, DefaultText will be + * shown instead of todays date and time. + * + * @author Xiang Wei Zhuo + * @version v1.0, last update on Sat Dec 11 15:25:11 EST 2004 + * @package System.I18N + */ +class TDateFormat extends TI18NControl implements IDataRenderer +{ + /** + * Default DateFormat, set to the application culture. + * @var DateFormat + */ + protected static $formatter; + + /** + * A set of pattern presets and their respective formatting shorthand. + * @var array + */ + static private $_patternPresets = array( + 'fulldate'=>'P','full'=>'P', + 'longdate'=>'D','long'=>'d', + 'mediumdate'=>'p','medium'=>'p', + 'shortdate'=>'d','short'=>'d', + 'fulltime'=>'Q', 'longtime'=>'T', + 'mediumtime'=>'q', 'shorttime'=>'t'); + + /** + * Sets the date time formatting pattern. + * @param string format pattern. + */ + public function setPattern($value) + { + $this->setViewState('Pattern',$value,''); + } + + /** + * Gets the date time format pattern. + * @return string format pattern. + */ + public function getPattern() + { + $string = $this->getViewState('Pattern',''); + + $pattern = null; + + //try the subpattern of "date time" presets + $subpatterns = explode(' ',$string,2); + $datetime = array(); + if(count($subpatterns)==2) + { + $datetime[] = $this->getPreset($subpatterns[0]); + $datetime[] = $this->getPreset($subpatterns[1]); + } + + //we have a good subpattern + if(count($datetime) == 2 + && strlen($datetime[0]) == 1 + && strlen($datetime[1]) == 1) + { + $pattern = $datetime; + } + else //no subpattern, try the presets + $pattern = $this->getPreset($string); + + //no presets found, use the string as the pattern + //and let the DateFormat handle it. + if($pattern===null) + $pattern = $string; + if (!is_array($pattern) && strlen($pattern) == 0) + $pattern = null; + return $pattern; + } + + /** + * For a given string, try and find a preset pattern. + * @param string the preset pattern name + * @return string a preset pattern if found, null otherwise. + */ + protected function getPreset($string) + { + $string = strtolower($string); + foreach(self::$_patternPresets as $pattern => $preset) + { + if($string == $pattern) + return $preset; + } + } + + /** + * Get the date-time value for this control. + * @return string date time value. + */ + public function getValue() + { + $value = $this->getViewState('Value',''); + if(empty($value)) + { + $defaultText = $this->getDefaultText(); + if(empty($defaultText)) + return time(); + } + return $value; + } + + /** + * Set the date-time value for this control. + * @param string the date-time value. + */ + public function setValue($value) + { + $this->setViewState('Value',$value,''); + } + + /** + * Get the default text value for this control. + * @return string default text value + */ + public function getDefaultText() + { + return $this->getViewState('DefaultText',''); + } + + /** + * Set the default text value for this control. + * @param string default text value + */ + public function setDefaultText($value) + { + $this->setViewState('DefaultText',$value,''); + } + + /** + * Get the date-time value for this control. + * This method is required by {@link IDataRenderer}. + * It is the same as {@link getValue()}. + * @return string date time value. + * @see getValue + * @since 3.1.2 + */ + public function getData() + { + return $this->getValue(); + } + + /** + * Set the date-time value for this control. + * This method is required by {@link IDataRenderer}. + * It is the same as {@link setValue()}. + * @param string the date-time value. + * @see setValue + * @since 3.1.2 + */ + public function setData($value) + { + $this->setValue($value); + } + + /** + * Renders the localized version of the date-time value. + * If the culture is not specified, the default application + * culture will be used. + * This method overrides parent's implementation. + */ + protected function getFormattedDate() + { + $value = $this->getValue(); + $defaultText = $this->getDefaultText(); + if(empty($value) && !empty($defaultText)) + return $this->getDefaultText(); + + $app = $this->getApplication()->getGlobalization(); + + //initialized the default class wide formatter + if(self::$formatter===null) + self::$formatter = new DateFormat($app->getCulture()); + + $culture = $this->getCulture(); + + //return the specific cultural formatted date time + if(strlen($culture) && $app->getCulture() !== $culture) + { + $formatter = new DateFormat($culture); + return $formatter->format($value, + $this->getPattern(), + $this->getCharset()); + } + //return the application wide culture formatted date time. + $result = self::$formatter->format($value, + $this->getPattern(), + $this->getCharset()); + return $result; + } + + public function render($writer) + { + $writer->write($this->getFormattedDate()); + } + } \ No newline at end of file diff --git a/framework/I18N/TGlobalization.php b/framework/I18N/TGlobalization.php index 54f5d66f..d5496353 100644 --- a/framework/I18N/TGlobalization.php +++ b/framework/I18N/TGlobalization.php @@ -1,299 +1,299 @@ - - * @link http://www.pradosoft.com/ - * @copyright Copyright © 2005-2012 PradoSoft - * @license http://www.pradosoft.com/license/ - * @version $Id$ - * @package System.I18N - */ - - -/** - * TGlobalization contains settings for Culture, Charset - * and TranslationConfiguration. - * - * TGlobalization can be subclassed to change how the Culture, Charset - * are determined. See TGlobalizationAutoDetect for example of - * setting the Culture based on browser settings. - * - * @author Wei Zhuo - * @version $Revision: 1.66 $ $Date: ${DATE} ${TIME} $ - * @package System.I18N - * @since 3.0 - */ -class TGlobalization extends TModule -{ - /** - * Default character set is 'UTF-8'. - * @var string - */ - private $_defaultCharset = 'UTF-8'; - - /** - * Default culture is 'en'. - * @var string - */ - private $_defaultCulture = 'en'; - - /** - * The current charset. - * @var string - */ - private $_charset=null; - - /** - * The current culture. - * @var string - */ - private $_culture=null; - - /** - * Translation source parameters. - * @var TMap - */ - private $_translation; - - /** - * @var boolean whether we should translate the default culture - */ - private $_translateDefaultCulture=true; - - /** - * Initialize the Culture and Charset for this application. - * You should override this method if you want a different way of - * setting the Culture and/or Charset for your application. - * If you override this method, call parent::init($xml) first. - * @param mixed application configuration - */ - public function init($config) - { - if($this->_charset===null) - $this->_charset=$this->getDefaultCharset(); - if($this->_culture===null) - $this->_culture=$this->getDefaultCulture(); - - if($config!==null) - { - if($this->getApplication()->getConfigurationType()==TApplication::CONFIG_TYPE_PHP) - $translation = isset($config['translate'])?$config['translate']:null; - else - { - $t = $config->getElementByTagName('translation'); - $translation = ($t)?$t->getAttributes():null; - } - if($translation) - $this->setTranslationConfiguration($translation); - } - $this->getApplication()->setGlobalization($this); - } - - /** - * @return string default culture - */ - public function getTranslateDefaultCulture() - { - return $this->_translateDefaultCulture; - } - - /** - * @param bool default culture, e.g. en_US for American English - */ - public function setTranslateDefaultCulture($value) - { - $this->_translateDefaultCulture = TPropertyValue::ensureBoolean($value); - } - - /** - * @return string default culture - */ - public function getDefaultCulture() - { - return $this->_defaultCulture; - } - - /** - * @param string default culture, e.g. en_US for American English - */ - public function setDefaultCulture($culture) - { - $this->_defaultCulture = str_replace('-','_',$culture); - } - - /** - * @return string default charset set - */ - public function getDefaultCharset() - { - return $this->_defaultCharset; - } - - /** - * @param string default localization charset, e.g. UTF-8 - */ - public function setDefaultCharset($charset) - { - $this->_defaultCharset = $charset; - } - - /** - * @return string current application culture - */ - public function getCulture() - { - return $this->_culture; - } - - /** - * @param string culture, e.g. en_US for American English - */ - public function setCulture($culture) - { - $this->_culture = str_replace('-','_',$culture); - } - - /** - * @return string localization charset - */ - public function getCharset() - { - return $this->_charset; - } - - /** - * @param string localization charset, e.g. UTF-8 - */ - public function setCharset($charset) - { - $this->_charset = $charset; - } - - /** - * @return TMap translation source configuration. - */ - public function getTranslationConfiguration() - { - return (!$this->_translateDefaultCulture && ($this->getDefaultCulture() == $this->getCulture())) - ? null - : $this->_translation; - } - - /** - * Sets the translation configuration. Example configuration: - * - * $config['type'] = 'XLIFF'; //XLIFF, gettext, Database or MySQL (deprecated) - * $config['source'] = 'Path.to.directory'; // for types XLIFF and gettext - * $config['source'] = 'connectionId'; // for type Database - * $config['source'] = 'mysql://user:pw@host/db'; // for type MySQL (deprecated) - * $config['catalogue'] = 'messages'; //default catalog - * $config['autosave'] = 'true'; //save untranslated message - * $config['cache'] = 'true'; //cache translated message - * $config['marker'] = '@@'; // surround untranslated text with '@@' - * - * Throws exception is source is not found. - * @param TMap|array configuration options - */ - protected function setTranslationConfiguration($config) - { - if($config['type'] == 'XLIFF' || $config['type'] == 'gettext') - { - if($config['source']) - { - $config['source'] = Prado::getPathOfNamespace($config['source']); - if(!is_dir($config['source'])) - { - if(@mkdir($config['source'])===false) - throw new TConfigurationException('globalization_source_path_failed', - $config['source']); - chmod($config['source'], PRADO_CHMOD); //make it deletable - } - } - else - { - throw new TConfigurationException("invalid source dir '{$config['source']}'"); - } - } - if($config['cache']) - { - $config['cache'] = $this->getApplication()->getRunTimePath().'/i18n'; - if(!is_dir($config['cache'])) - { - if(@mkdir($config['cache'])===false) - throw new TConfigurationException('globalization_cache_path_failed', - $config['cache']); - chmod($config['cache'], PRADO_CHMOD); //make it deletable - } - } - $this->_translation = $config; - } - - /** - * @return string current translation catalogue. - */ - public function getTranslationCatalogue() - { - return $this->_translation['catalogue']; - } - - /** - * @param string update the translation catalogue. - */ - public function setTranslationCatalogue($value) - { - $this->_translation['catalogue'] = $value; - } - - /** - * Gets all the variants of a specific culture. If the parameter - * $culture is null, the current culture is used. - * @param string $culture the Culture string - * @return array variants of the culture. - */ - public function getCultureVariants($culture=null) - { - if($culture===null) $culture = $this->getCulture(); - $variants = explode('_', $culture); - $result = array(); - for(; count($variants) > 0; array_pop($variants)) - $result[] = implode('_', $variants); - return $result; - } - - /** - * Returns a list of possible localized files. Example - * - * $files = $app->getLocalizedResource("path/to/Home.page","en_US"); - * - * will return - *
-	 * array
-	 *   0 => 'path/to/en_US/Home.page'
-	 *   1 => 'path/to/en/Home.page'
-	 *   2 => 'path/to/Home.en_US.page'
-	 *   3 => 'path/to/Home.en.page'
-	 *   4 => 'path/to/Home.page'
-	 * 
- * Note that you still need to verify the existance of these files. - * @param string filename - * @param string culture string, null to use current culture - * @return array list of possible localized resource files. - */ - public function getLocalizedResource($file,$culture=null) - { - $files = array(); - $variants = $this->getCultureVariants($culture); - $path = pathinfo($file); - foreach($variants as $variant) - $files[] = $path['dirname'].DIRECTORY_SEPARATOR.$variant.DIRECTORY_SEPARATOR.$path['basename']; - $filename = substr($path['basename'],0,strrpos($path['basename'],'.')); - foreach($variants as $variant) - $files[] = $path['dirname'].DIRECTORY_SEPARATOR.$filename.'.'.$variant.'.'.$path['extension']; - $files[] = $file; - return $files; - } - -} - -?> + + * @link http://www.pradosoft.com/ + * @copyright Copyright © 2005-2012 PradoSoft + * @license http://www.pradosoft.com/license/ + * @version $Id$ + * @package System.I18N + */ + + +/** + * TGlobalization contains settings for Culture, Charset + * and TranslationConfiguration. + * + * TGlobalization can be subclassed to change how the Culture, Charset + * are determined. See TGlobalizationAutoDetect for example of + * setting the Culture based on browser settings. + * + * @author Wei Zhuo + * @version $Revision: 1.66 $ $Date: ${DATE} ${TIME} $ + * @package System.I18N + * @since 3.0 + */ +class TGlobalization extends TModule +{ + /** + * Default character set is 'UTF-8'. + * @var string + */ + private $_defaultCharset = 'UTF-8'; + + /** + * Default culture is 'en'. + * @var string + */ + private $_defaultCulture = 'en'; + + /** + * The current charset. + * @var string + */ + private $_charset=null; + + /** + * The current culture. + * @var string + */ + private $_culture=null; + + /** + * Translation source parameters. + * @var TMap + */ + private $_translation; + + /** + * @var boolean whether we should translate the default culture + */ + private $_translateDefaultCulture=true; + + /** + * Initialize the Culture and Charset for this application. + * You should override this method if you want a different way of + * setting the Culture and/or Charset for your application. + * If you override this method, call parent::init($xml) first. + * @param mixed application configuration + */ + public function init($config) + { + if($this->_charset===null) + $this->_charset=$this->getDefaultCharset(); + if($this->_culture===null) + $this->_culture=$this->getDefaultCulture(); + + if($config!==null) + { + if($this->getApplication()->getConfigurationType()==TApplication::CONFIG_TYPE_PHP) + $translation = isset($config['translate'])?$config['translate']:null; + else + { + $t = $config->getElementByTagName('translation'); + $translation = ($t)?$t->getAttributes():null; + } + if($translation) + $this->setTranslationConfiguration($translation); + } + $this->getApplication()->setGlobalization($this); + } + + /** + * @return string default culture + */ + public function getTranslateDefaultCulture() + { + return $this->_translateDefaultCulture; + } + + /** + * @param bool default culture, e.g. en_US for American English + */ + public function setTranslateDefaultCulture($value) + { + $this->_translateDefaultCulture = TPropertyValue::ensureBoolean($value); + } + + /** + * @return string default culture + */ + public function getDefaultCulture() + { + return $this->_defaultCulture; + } + + /** + * @param string default culture, e.g. en_US for American English + */ + public function setDefaultCulture($culture) + { + $this->_defaultCulture = str_replace('-','_',$culture); + } + + /** + * @return string default charset set + */ + public function getDefaultCharset() + { + return $this->_defaultCharset; + } + + /** + * @param string default localization charset, e.g. UTF-8 + */ + public function setDefaultCharset($charset) + { + $this->_defaultCharset = $charset; + } + + /** + * @return string current application culture + */ + public function getCulture() + { + return $this->_culture; + } + + /** + * @param string culture, e.g. en_US for American English + */ + public function setCulture($culture) + { + $this->_culture = str_replace('-','_',$culture); + } + + /** + * @return string localization charset + */ + public function getCharset() + { + return $this->_charset; + } + + /** + * @param string localization charset, e.g. UTF-8 + */ + public function setCharset($charset) + { + $this->_charset = $charset; + } + + /** + * @return TMap translation source configuration. + */ + public function getTranslationConfiguration() + { + return (!$this->_translateDefaultCulture && ($this->getDefaultCulture() == $this->getCulture())) + ? null + : $this->_translation; + } + + /** + * Sets the translation configuration. Example configuration: + * + * $config['type'] = 'XLIFF'; //XLIFF, gettext, Database or MySQL (deprecated) + * $config['source'] = 'Path.to.directory'; // for types XLIFF and gettext + * $config['source'] = 'connectionId'; // for type Database + * $config['source'] = 'mysql://user:pw@host/db'; // for type MySQL (deprecated) + * $config['catalogue'] = 'messages'; //default catalog + * $config['autosave'] = 'true'; //save untranslated message + * $config['cache'] = 'true'; //cache translated message + * $config['marker'] = '@@'; // surround untranslated text with '@@' + * + * Throws exception is source is not found. + * @param TMap|array configuration options + */ + protected function setTranslationConfiguration($config) + { + if($config['type'] == 'XLIFF' || $config['type'] == 'gettext') + { + if($config['source']) + { + $config['source'] = Prado::getPathOfNamespace($config['source']); + if(!is_dir($config['source'])) + { + if(@mkdir($config['source'])===false) + throw new TConfigurationException('globalization_source_path_failed', + $config['source']); + chmod($config['source'], PRADO_CHMOD); //make it deletable + } + } + else + { + throw new TConfigurationException("invalid source dir '{$config['source']}'"); + } + } + if($config['cache']) + { + $config['cache'] = $this->getApplication()->getRunTimePath().'/i18n'; + if(!is_dir($config['cache'])) + { + if(@mkdir($config['cache'])===false) + throw new TConfigurationException('globalization_cache_path_failed', + $config['cache']); + chmod($config['cache'], PRADO_CHMOD); //make it deletable + } + } + $this->_translation = $config; + } + + /** + * @return string current translation catalogue. + */ + public function getTranslationCatalogue() + { + return $this->_translation['catalogue']; + } + + /** + * @param string update the translation catalogue. + */ + public function setTranslationCatalogue($value) + { + $this->_translation['catalogue'] = $value; + } + + /** + * Gets all the variants of a specific culture. If the parameter + * $culture is null, the current culture is used. + * @param string $culture the Culture string + * @return array variants of the culture. + */ + public function getCultureVariants($culture=null) + { + if($culture===null) $culture = $this->getCulture(); + $variants = explode('_', $culture); + $result = array(); + for(; count($variants) > 0; array_pop($variants)) + $result[] = implode('_', $variants); + return $result; + } + + /** + * Returns a list of possible localized files. Example + * + * $files = $app->getLocalizedResource("path/to/Home.page","en_US"); + * + * will return + *
+	 * array
+	 *   0 => 'path/to/en_US/Home.page'
+	 *   1 => 'path/to/en/Home.page'
+	 *   2 => 'path/to/Home.en_US.page'
+	 *   3 => 'path/to/Home.en.page'
+	 *   4 => 'path/to/Home.page'
+	 * 
+ * Note that you still need to verify the existance of these files. + * @param string filename + * @param string culture string, null to use current culture + * @return array list of possible localized resource files. + */ + public function getLocalizedResource($file,$culture=null) + { + $files = array(); + $variants = $this->getCultureVariants($culture); + $path = pathinfo($file); + foreach($variants as $variant) + $files[] = $path['dirname'].DIRECTORY_SEPARATOR.$variant.DIRECTORY_SEPARATOR.$path['basename']; + $filename = substr($path['basename'],0,strrpos($path['basename'],'.')); + foreach($variants as $variant) + $files[] = $path['dirname'].DIRECTORY_SEPARATOR.$filename.'.'.$variant.'.'.$path['extension']; + $files[] = $file; + return $files; + } + +} + +?> diff --git a/framework/I18N/TGlobalizationAutoDetect.php b/framework/I18N/TGlobalizationAutoDetect.php index 36273867..5d8a7677 100644 --- a/framework/I18N/TGlobalizationAutoDetect.php +++ b/framework/I18N/TGlobalizationAutoDetect.php @@ -1,49 +1,49 @@ - - * @link http://www.pradosoft.com/ + + * @link http://www.pradosoft.com/ * @copyright Copyright © 2005-2012 PradoSoft - * @license http://www.pradosoft.com/license/ - * @version $Revision: 1.66 $ $Date: ${DATE} ${TIME} $ - * @package System.I18N - */ - -/** - * Import the HTTPNeogtiator - */ -Prado::using('System.I18N.core.HTTPNegotiator'); - -/** - * TGlobalizationAutoDetect class will automatically try to resolve the default - * culture using the user browser language settings. - * - * @author Wei Zhuo - * @version $Revision: 1.66 $ $Date: ${DATE} ${TIME} $ - * @package System.I18N - */ -class TGlobalizationAutoDetect extends TGlobalization -{ - private $_detectedLanguage; - - public function init($xml) - { - parent::init($xml); - - //set the culture according to browser language settings - $http = new HTTPNegotiator(); - $languages = $http->getLanguages(); - if(count($languages) > 0) - { - $this->_detectedLanguage=$languages[0]; - $this->setCulture($languages[0]); - } - } - - public function getDetectedLanguage() - { - return $this->_detectedLanguage; - } -} - + * @license http://www.pradosoft.com/license/ + * @version $Revision: 1.66 $ $Date: ${DATE} ${TIME} $ + * @package System.I18N + */ + +/** + * Import the HTTPNeogtiator + */ +Prado::using('System.I18N.core.HTTPNegotiator'); + +/** + * TGlobalizationAutoDetect class will automatically try to resolve the default + * culture using the user browser language settings. + * + * @author Wei Zhuo + * @version $Revision: 1.66 $ $Date: ${DATE} ${TIME} $ + * @package System.I18N + */ +class TGlobalizationAutoDetect extends TGlobalization +{ + private $_detectedLanguage; + + public function init($xml) + { + parent::init($xml); + + //set the culture according to browser language settings + $http = new HTTPNegotiator(); + $languages = $http->getLanguages(); + if(count($languages) > 0) + { + $this->_detectedLanguage=$languages[0]; + $this->setCulture($languages[0]); + } + } + + public function getDetectedLanguage() + { + return $this->_detectedLanguage; + } +} + diff --git a/framework/I18N/TI18NControl.php b/framework/I18N/TI18NControl.php index 7e8efe0a..79c7d5ed 100644 --- a/framework/I18N/TI18NControl.php +++ b/framework/I18N/TI18NControl.php @@ -1,90 +1,90 @@ - - * @link http://www.pradosoft.com/ - * @copyright Copyright © 2005-2012 PradoSoft - * @license http://www.pradosoft.com/license/ - * @version $Id$ - * @package System.I18N - */ - - -/** - * TI18NControl class. - * - * Base class for I18N components, providing Culture and Charset properties. - * Namespace: System.I18N - * - * Properties - * - Culture, string, - *
Gets or sets the culture for formatting. If the Culture property - * is not specified. The culture from the Application/Page is used. - * - Charset, string, - *
Gets or sets the charset for both input and output. - * If the Charset property is not specified. The charset from the - * Application/Page is used. The default is UTF-8. - * - * @author Xiang Wei Zhuo - * @version v1.0, last update on Sat Dec 11 15:25:11 EST 2004 - * @package System.I18N - */ -class TI18NControl extends TControl -{ - /** - * Gets the charset. - * It is evaluated in the following order: - * 1) application charset, - * 2) the default charset in globalization - * 3) UTF-8 - * @return string charset - */ - public function getCharset() - { - $app = $this->getApplication()->getGlobalization(false); - - //instance charset - $charset = $this->getViewState('Charset',''); - - //fall back to globalization charset - if(empty($charset)) - $charset = ($app===null) ? '' : $app->getCharset(); - - //fall back to default charset - if(empty($charset)) - $charset = ($app===null) ? 'UTF-8' : $app->getDefaultCharset(); - - return $charset; - } - - /** - * Sets the charset for message output - * @param string the charset, e.g. UTF-8 - */ - public function setCharset($value) - { - $this->setViewState('Charset',$value,''); - } - - - /** - * Get the specific culture for this control. - * @param parameter - * @return string culture identifier. - */ - public function getCulture() - { - return $this->getViewState('Culture',''); - } - - /** - * Get the custom culture identifier. - * @param string culture identifier. - */ - public function setCulture($culture) - { - $this->setViewState('Culture',$culture,''); - } -} - + + * @link http://www.pradosoft.com/ + * @copyright Copyright © 2005-2012 PradoSoft + * @license http://www.pradosoft.com/license/ + * @version $Id$ + * @package System.I18N + */ + + +/** + * TI18NControl class. + * + * Base class for I18N components, providing Culture and Charset properties. + * Namespace: System.I18N + * + * Properties + * - Culture, string, + *
Gets or sets the culture for formatting. If the Culture property + * is not specified. The culture from the Application/Page is used. + * - Charset, string, + *
Gets or sets the charset for both input and output. + * If the Charset property is not specified. The charset from the + * Application/Page is used. The default is UTF-8. + * + * @author Xiang Wei Zhuo + * @version v1.0, last update on Sat Dec 11 15:25:11 EST 2004 + * @package System.I18N + */ +class TI18NControl extends TControl +{ + /** + * Gets the charset. + * It is evaluated in the following order: + * 1) application charset, + * 2) the default charset in globalization + * 3) UTF-8 + * @return string charset + */ + public function getCharset() + { + $app = $this->getApplication()->getGlobalization(false); + + //instance charset + $charset = $this->getViewState('Charset',''); + + //fall back to globalization charset + if(empty($charset)) + $charset = ($app===null) ? '' : $app->getCharset(); + + //fall back to default charset + if(empty($charset)) + $charset = ($app===null) ? 'UTF-8' : $app->getDefaultCharset(); + + return $charset; + } + + /** + * Sets the charset for message output + * @param string the charset, e.g. UTF-8 + */ + public function setCharset($value) + { + $this->setViewState('Charset',$value,''); + } + + + /** + * Get the specific culture for this control. + * @param parameter + * @return string culture identifier. + */ + public function getCulture() + { + return $this->getViewState('Culture',''); + } + + /** + * Get the custom culture identifier. + * @param string culture identifier. + */ + public function setCulture($culture) + { + $this->setViewState('Culture',$culture,''); + } +} + diff --git a/framework/I18N/TNumberFormat.php b/framework/I18N/TNumberFormat.php index 30a1e638..1c2502be 100644 --- a/framework/I18N/TNumberFormat.php +++ b/framework/I18N/TNumberFormat.php @@ -1,251 +1,251 @@ - - * @link http://www.pradosoft.com/ - * @copyright Copyright © 2005-2012 PradoSoft - * @license http://www.pradosoft.com/license/ - * @version $Id$ - * @package System.I18N - */ - -/** - * Get the NumberFormat class. - */ -Prado::using('System.I18N.core.NumberFormat'); - -/** - * Get the parent control class. - */ -Prado::using('System.I18N.TI18NControl'); - -/** - * To format numbers in locale sensitive manner use - * - * - * - * - * Numbers can be formatted as currency, percentage, decimal or scientific - * numbers by specifying the Type attribute. The known types are - * "currency", "percentage", "decimal" and "scientific". - * - * If someone from US want to see sales figures from a store in - * Germany (say using the EURO currency), formatted using the german - * currency, you would need to use the attribute Culture="de_DE" to get - * the currency right, e.g. 100,00. The decimal and grouping separator is - * then also from the de_DE locale. This may lead to some confusion because - * people from US know the "," as thousand separator. Therefore a "Currency" - * attribute is available, so that the output from the following example - * results in 100.00. - * - * - * - * - * Namespace: System.I18N - * - * Properties - * - Value, number, - *
Gets or sets the number to format. The tag content is used as Value - * if the Value property is not specified. - * - Type, string, - *
Gets or sets the formatting type. The valid types are - * 'decimal', 'currency', 'percentage' and 'scientific'. - * - Currency, string, - *
Gets or sets the currency symbol for the currency format. - * The default is 'USD' if the Currency property is not specified. - * - Pattern, string, - *
Gets or sets the custom number formatting pattern. - * - DefaultText, string, - *
Gets or sets the default text. If Value is not set, DefaultText will be - * shown instead of the default currency Value/Pattern. - * - * @author Xiang Wei Zhuo - * @version v1.0, last update on Sat Dec 11 17:49:56 EST 2004 - * @package System.I18N - */ -class TNumberFormat extends TI18NControl implements IDataRenderer -{ - /** - * Default NumberFormat, set to the application culture. - * @var NumberFormat - */ - protected static $formatter; - - /** - * Get the number formatting pattern. - * @return string format pattern. - */ - public function getPattern() - { - return $this->getViewState('Pattern',''); - } - - /** - * Set the number format pattern. - * @param string format pattern. - */ - public function setPattern($pattern) - { - $this->setViewState('Pattern',$pattern,''); - } - - /** - * Get the numberic value for this control. - * @return string number - */ - public function getValue() - { - return $this->getViewState('Value',''); - } - - /** - * Set the numberic value for this control. - * @param string the number value - */ - public function setValue($value) - { - $this->setViewState('Value',$value,''); - } - - /** - * Get the default text value for this control. - * @return string default text value - */ - public function getDefaultText() - { - return $this->getViewState('DefaultText',''); - } - - /** - * Set the default text value for this control. - * @param string default text value - */ - public function setDefaultText($value) - { - $this->setViewState('DefaultText',$value,''); - } - - /** - * Get the numberic value for this control. - * This method is required by {@link IDataRenderer}. - * It is the same as {@link getValue()}. - * @return string number - * @see getValue - * @since 3.1.2 - */ - public function getData() - { - return $this->getValue(); - } - - /** - * Set the numberic value for this control. - * This method is required by {@link IDataRenderer}. - * It is the same as {@link setValue()}. - * @param string the number value - * @see setValue - * @since 3.1.2 - */ - public function setData($value) - { - $this->setValue($value); - } - - /** - * Get the formatting type for this control. - * @return string formatting type. - */ - public function getType() - { - return $this->getViewState('Type','d'); - } - - /** - * Set the formatting type for this control. - * @param string formatting type, either "decimal", "currency","percentage" - * or "scientific" - * @throws TPropertyTypeInvalidException - */ - public function setType($type) - { - $type = strtolower($type); - - switch($type) - { - case 'decimal': - $this->setViewState('Type','d',''); break; - case 'currency': - $this->setViewState('Type','c',''); break; - case 'percentage': - $this->setViewState('Type','p',''); break; - case 'scientific': - $this->setViewState('Type','e',''); break; - default: - throw new TInvalidDataValueException('numberformat_type_invalid',$type); - } - - } - - /** - * @return string 3 letter currency code. Defaults to 'USD'. - */ - public function getCurrency() - { - return $this->getViewState('Currency','USD'); - } - - /** - * Set the 3-letter ISO 4217 code. For example, the code - * "USD" represents the US Dollar and "EUR" represents the Euro currency. - * @param string currency code. - */ - public function setCurrency($currency) - { - $this->setViewState('Currency', $currency,''); - } - - /** - * Formats the localized number, be it currency or decimal, or percentage. - * If the culture is not specified, the default application - * culture will be used. - * @return string formatted number - */ - protected function getFormattedValue() - { - $value = $this->getValue(); - $defaultText = $this->getDefaultText(); - if(empty($value) && !empty($defaultText)) - return $this->getDefaultText(); - - $app = $this->getApplication()->getGlobalization(); - //initialized the default class wide formatter - if(self::$formatter===null) - self::$formatter = new NumberFormat($app->getCulture()); - - $pattern = strlen($this->getPattern()) > 0 - ? $this->getPattern() : $this->getType(); - - $culture = $this->getCulture(); - //return the specific cultural formatted number - if(!empty($culture) && $app->getCulture() != $culture) - { - $formatter = new NumberFormat($culture); - return $formatter->format($this->getValue(),$pattern, - $this->getCurrency(), - $this->getCharset()); - } - - //return the application wide culture formatted number. - return self::$formatter->format($this->getValue(),$pattern, - $this->getCurrency(), - $this->getCharset()); - } - - public function render($writer) - { - $writer->write($this->getFormattedValue()); - } -} - -?> + + * @link http://www.pradosoft.com/ + * @copyright Copyright © 2005-2012 PradoSoft + * @license http://www.pradosoft.com/license/ + * @version $Id$ + * @package System.I18N + */ + +/** + * Get the NumberFormat class. + */ +Prado::using('System.I18N.core.NumberFormat'); + +/** + * Get the parent control class. + */ +Prado::using('System.I18N.TI18NControl'); + +/** + * To format numbers in locale sensitive manner use + * + * + * + * + * Numbers can be formatted as currency, percentage, decimal or scientific + * numbers by specifying the Type attribute. The known types are + * "currency", "percentage", "decimal" and "scientific". + * + * If someone from US want to see sales figures from a store in + * Germany (say using the EURO currency), formatted using the german + * currency, you would need to use the attribute Culture="de_DE" to get + * the currency right, e.g. 100,00. The decimal and grouping separator is + * then also from the de_DE locale. This may lead to some confusion because + * people from US know the "," as thousand separator. Therefore a "Currency" + * attribute is available, so that the output from the following example + * results in 100.00. + * + * + * + * + * Namespace: System.I18N + * + * Properties + * - Value, number, + *
Gets or sets the number to format. The tag content is used as Value + * if the Value property is not specified. + * - Type, string, + *
Gets or sets the formatting type. The valid types are + * 'decimal', 'currency', 'percentage' and 'scientific'. + * - Currency, string, + *
Gets or sets the currency symbol for the currency format. + * The default is 'USD' if the Currency property is not specified. + * - Pattern, string, + *
Gets or sets the custom number formatting pattern. + * - DefaultText, string, + *
Gets or sets the default text. If Value is not set, DefaultText will be + * shown instead of the default currency Value/Pattern. + * + * @author Xiang Wei Zhuo + * @version v1.0, last update on Sat Dec 11 17:49:56 EST 2004 + * @package System.I18N + */ +class TNumberFormat extends TI18NControl implements IDataRenderer +{ + /** + * Default NumberFormat, set to the application culture. + * @var NumberFormat + */ + protected static $formatter; + + /** + * Get the number formatting pattern. + * @return string format pattern. + */ + public function getPattern() + { + return $this->getViewState('Pattern',''); + } + + /** + * Set the number format pattern. + * @param string format pattern. + */ + public function setPattern($pattern) + { + $this->setViewState('Pattern',$pattern,''); + } + + /** + * Get the numberic value for this control. + * @return string number + */ + public function getValue() + { + return $this->getViewState('Value',''); + } + + /** + * Set the numberic value for this control. + * @param string the number value + */ + public function setValue($value) + { + $this->setViewState('Value',$value,''); + } + + /** + * Get the default text value for this control. + * @return string default text value + */ + public function getDefaultText() + { + return $this->getViewState('DefaultText',''); + } + + /** + * Set the default text value for this control. + * @param string default text value + */ + public function setDefaultText($value) + { + $this->setViewState('DefaultText',$value,''); + } + + /** + * Get the numberic value for this control. + * This method is required by {@link IDataRenderer}. + * It is the same as {@link getValue()}. + * @return string number + * @see getValue + * @since 3.1.2 + */ + public function getData() + { + return $this->getValue(); + } + + /** + * Set the numberic value for this control. + * This method is required by {@link IDataRenderer}. + * It is the same as {@link setValue()}. + * @param string the number value + * @see setValue + * @since 3.1.2 + */ + public function setData($value) + { + $this->setValue($value); + } + + /** + * Get the formatting type for this control. + * @return string formatting type. + */ + public function getType() + { + return $this->getViewState('Type','d'); + } + + /** + * Set the formatting type for this control. + * @param string formatting type, either "decimal", "currency","percentage" + * or "scientific" + * @throws TPropertyTypeInvalidException + */ + public function setType($type) + { + $type = strtolower($type); + + switch($type) + { + case 'decimal': + $this->setViewState('Type','d',''); break; + case 'currency': + $this->setViewState('Type','c',''); break; + case 'percentage': + $this->setViewState('Type','p',''); break; + case 'scientific': + $this->setViewState('Type','e',''); break; + default: + throw new TInvalidDataValueException('numberformat_type_invalid',$type); + } + + } + + /** + * @return string 3 letter currency code. Defaults to 'USD'. + */ + public function getCurrency() + { + return $this->getViewState('Currency','USD'); + } + + /** + * Set the 3-letter ISO 4217 code. For example, the code + * "USD" represents the US Dollar and "EUR" represents the Euro currency. + * @param string currency code. + */ + public function setCurrency($currency) + { + $this->setViewState('Currency', $currency,''); + } + + /** + * Formats the localized number, be it currency or decimal, or percentage. + * If the culture is not specified, the default application + * culture will be used. + * @return string formatted number + */ + protected function getFormattedValue() + { + $value = $this->getValue(); + $defaultText = $this->getDefaultText(); + if(empty($value) && !empty($defaultText)) + return $this->getDefaultText(); + + $app = $this->getApplication()->getGlobalization(); + //initialized the default class wide formatter + if(self::$formatter===null) + self::$formatter = new NumberFormat($app->getCulture()); + + $pattern = strlen($this->getPattern()) > 0 + ? $this->getPattern() : $this->getType(); + + $culture = $this->getCulture(); + //return the specific cultural formatted number + if(!empty($culture) && $app->getCulture() != $culture) + { + $formatter = new NumberFormat($culture); + return $formatter->format($this->getValue(),$pattern, + $this->getCurrency(), + $this->getCharset()); + } + + //return the application wide culture formatted number. + return self::$formatter->format($this->getValue(),$pattern, + $this->getCurrency(), + $this->getCharset()); + } + + public function render($writer) + { + $writer->write($this->getFormattedValue()); + } +} + +?> diff --git a/framework/I18N/TTranslate.php b/framework/I18N/TTranslate.php index 9ecb8a29..2976e8c3 100644 --- a/framework/I18N/TTranslate.php +++ b/framework/I18N/TTranslate.php @@ -1,255 +1,255 @@ - - * @link http://www.pradosoft.com/ - * @copyright Copyright © 2005-2012 PradoSoft - * @license http://www.pradosoft.com/license/ - * @version $Id$ - * @package System.I18N - */ - -/** - * Get the parent control class. - */ -Prado::using('System.I18N.TI18NControl'); - -/** - * TTranslate class. - * - * This component performs message/string translation. The translation - * source is set in the TGlobalization handler. The following example - * demonstrated a simple message translation. - * - * - * - * - * Depending on the culture set on the page, the phrase "Goodbye" will - * be translated. - * - * The {@link getParameters Parameters} property can be use to add name values pairs for - * substitution. Substrings enclosed with "{" and "}" in the translation message are consider as the - * parameter names during substitution lookup. The following example will substitute the substring - * "{time}" with the value of the parameter attribute "Parameters.time=<%= time() %>. Note that - * the value of the parameter named "time" is evaluated. - * - * > - * The unix-time is "{time}". - * - * - * - * More complex string substitution can be applied using the - * TTranslateParameter component. - * - * Namespace: System.I18N - * - * Properties - * - Text, string, - *
Gets or sets the string to translate. - * - Catalogue, string, - *
Gets or sets the catalogue for message translation. The - * default catalogue can be set by the @Page directive. - * - Key, string, - *
Gets or sets the key used to message look up. - * - Trim, boolean, - *
Gets or sets an option to trim the contents. - * Default is to trim the contents. - * - * @author Xiang Wei Zhuo - * @version v1.0, last update on Fri Dec 24 21:38:49 EST 2004 - * @package System.I18N - */ -class TTranslate extends TI18NControl -{ - /** - * @return string the text to be localized/translated. - */ - public function getText() - { - return $this->getViewState('Text',''); - } - - /** - * Sets the text for localization. - * @param string the text for translation. - */ - public function setText($value) - { - $this->setViewState('Text',$value,''); - } - - /** - * Set the key for message lookup. - * @param string key - */ - public function setKey($value) - { - $this->setViewState('Key',$value,''); - } - - /** - * Get the key for message lookup. - * @return string key - */ - public function getKey() - { - return $this->getViewState('Key',''); - } - - /** - * Get the message catalogue. - * @return string catalogue. - */ - public function getCatalogue() - { - return $this->getViewState('Catalogue',''); - } - - /** - * Set the message catalogue. - * @param string catalogue. - */ - public function setCatalogue($value) - { - $this->setViewState('Catalogue',$value,''); - } - - /** - * Set the option to trim the contents. - * @param boolean trim or not. - */ - public function setTrim($value) - { - $this->setViewState('Trim',TPropertyValue::ensureBoolean($value),true); - } - - /** - * Trim the content or not. - * @return boolean trim or not. - */ - public function getTrim() - { - return $this->getViewState('Trim',true); - } - - /** - * Returns the list of custom parameters. - * Custom parameters are name-value pairs that may subsititute translation - * place holders during rendering. - * @return TAttributeCollection the list of custom parameters - */ - public function getParameters() - { - if($parameters=$this->getViewState('Parameters',null)) - return $parameters; - else - { - $parameters=new TAttributeCollection; - $parameters->setCaseSensitive(true); - $this->setViewState('Parameters',$parameters,null); - return $parameters; - } - } - - /** - * @return boolean whether the named parameter exists - */ - public function hasParameter($name) - { - if($parameters=$this->getViewState('Parameters',null)) - return $parameters->contains($name); - else - return false; - } - - /** - * @return string parameter value, null if parameter does not exist - */ - public function getParameter($name) - { - if($parameters=$this->getViewState('Parameters',null)) - return $parameters->itemAt($name); - else - return null; - } - - /** - * @param string parameter name - * @param string value of the parameter - */ - public function setParameter($name,$value) - { - $this->getParameters()->add($name,$value); - } - - /** - * Removes the named parameter. - * @param string the name of the parameter to be removed. - * @return string parameter value removed, null if parameter does not exist. - */ - public function removeParameter($name) - { - if($parameters=$this->getViewState('Parameters',null)) - return $parameters->remove($name); - else - return null; - } - - /** - * renders the translated string. - */ - public function render($writer) - { - $htmlWriter = Prado::createComponent($this->GetResponse()->getHtmlWriterType(), new TTextWriter()); - $subs = array(); - foreach($this->getParameters() as $key => $value) - $subs['{'.$key.'}'] = $value; - foreach($this->getControls() as $control) - { - if($control instanceof TTranslateParameter) - $subs['{'.$control->getKey().'}'] = $control->getParameter(); - elseif($control instanceof TControl) - $control->render($htmlWriter); - elseif(is_string($control)) - $htmlWriter->write($control); - } - - $text = $this->getText(); - if(strlen($text)==0) - $text = $htmlWriter->flush(); - if($this->getTrim()) - $text = trim($text); - - $writer->write($this->translateText($text, $subs)); - } - - /** - * Translates the text with subsititution. - * @param string text for translation - * @param array list of substitutions - * @return string translated text - */ - protected function translateText($text, $subs) - { - $app = $this->getApplication()->getGlobalization(); - - //no translation handler provided - if(($config = $app->getTranslationConfiguration())===null) - return strtr($text, $subs); - - $catalogue = $this->getCatalogue(); - if(empty($catalogue) && isset($config['catalogue'])) - $catalogue = $config['catalogue']; - if (empty($catalogue)) $catalogue='messages'; - Translation::init($catalogue); - - $key = $this->getKey(); - if(!empty($key)) $text = $key; - - //translate it - return Translation::formatter($catalogue)->format($text, - $subs, $catalogue, $this->getCharset()); - } -} - + + * @link http://www.pradosoft.com/ + * @copyright Copyright © 2005-2012 PradoSoft + * @license http://www.pradosoft.com/license/ + * @version $Id$ + * @package System.I18N + */ + +/** + * Get the parent control class. + */ +Prado::using('System.I18N.TI18NControl'); + +/** + * TTranslate class. + * + * This component performs message/string translation. The translation + * source is set in the TGlobalization handler. The following example + * demonstrated a simple message translation. + * + * + * + * + * Depending on the culture set on the page, the phrase "Goodbye" will + * be translated. + * + * The {@link getParameters Parameters} property can be use to add name values pairs for + * substitution. Substrings enclosed with "{" and "}" in the translation message are consider as the + * parameter names during substitution lookup. The following example will substitute the substring + * "{time}" with the value of the parameter attribute "Parameters.time=<%= time() %>. Note that + * the value of the parameter named "time" is evaluated. + * + * > + * The unix-time is "{time}". + * + * + * + * More complex string substitution can be applied using the + * TTranslateParameter component. + * + * Namespace: System.I18N + * + * Properties + * - Text, string, + *
Gets or sets the string to translate. + * - Catalogue, string, + *
Gets or sets the catalogue for message translation. The + * default catalogue can be set by the @Page directive. + * - Key, string, + *
Gets or sets the key used to message look up. + * - Trim, boolean, + *
Gets or sets an option to trim the contents. + * Default is to trim the contents. + * + * @author Xiang Wei Zhuo + * @version v1.0, last update on Fri Dec 24 21:38:49 EST 2004 + * @package System.I18N + */ +class TTranslate extends TI18NControl +{ + /** + * @return string the text to be localized/translated. + */ + public function getText() + { + return $this->getViewState('Text',''); + } + + /** + * Sets the text for localization. + * @param string the text for translation. + */ + public function setText($value) + { + $this->setViewState('Text',$value,''); + } + + /** + * Set the key for message lookup. + * @param string key + */ + public function setKey($value) + { + $this->setViewState('Key',$value,''); + } + + /** + * Get the key for message lookup. + * @return string key + */ + public function getKey() + { + return $this->getViewState('Key',''); + } + + /** + * Get the message catalogue. + * @return string catalogue. + */ + public function getCatalogue() + { + return $this->getViewState('Catalogue',''); + } + + /** + * Set the message catalogue. + * @param string catalogue. + */ + public function setCatalogue($value) + { + $this->setViewState('Catalogue',$value,''); + } + + /** + * Set the option to trim the contents. + * @param boolean trim or not. + */ + public function setTrim($value) + { + $this->setViewState('Trim',TPropertyValue::ensureBoolean($value),true); + } + + /** + * Trim the content or not. + * @return boolean trim or not. + */ + public function getTrim() + { + return $this->getViewState('Trim',true); + } + + /** + * Returns the list of custom parameters. + * Custom parameters are name-value pairs that may subsititute translation + * place holders during rendering. + * @return TAttributeCollection the list of custom parameters + */ + public function getParameters() + { + if($parameters=$this->getViewState('Parameters',null)) + return $parameters; + else + { + $parameters=new TAttributeCollection; + $parameters->setCaseSensitive(true); + $this->setViewState('Parameters',$parameters,null); + return $parameters; + } + } + + /** + * @return boolean whether the named parameter exists + */ + public function hasParameter($name) + { + if($parameters=$this->getViewState('Parameters',null)) + return $parameters->contains($name); + else + return false; + } + + /** + * @return string parameter value, null if parameter does not exist + */ + public function getParameter($name) + { + if($parameters=$this->getViewState('Parameters',null)) + return $parameters->itemAt($name); + else + return null; + } + + /** + * @param string parameter name + * @param string value of the parameter + */ + public function setParameter($name,$value) + { + $this->getParameters()->add($name,$value); + } + + /** + * Removes the named parameter. + * @param string the name of the parameter to be removed. + * @return string parameter value removed, null if parameter does not exist. + */ + public function removeParameter($name) + { + if($parameters=$this->getViewState('Parameters',null)) + return $parameters->remove($name); + else + return null; + } + + /** + * renders the translated string. + */ + public function render($writer) + { + $htmlWriter = Prado::createComponent($this->GetResponse()->getHtmlWriterType(), new TTextWriter()); + $subs = array(); + foreach($this->getParameters() as $key => $value) + $subs['{'.$key.'}'] = $value; + foreach($this->getControls() as $control) + { + if($control instanceof TTranslateParameter) + $subs['{'.$control->getKey().'}'] = $control->getParameter(); + elseif($control instanceof TControl) + $control->render($htmlWriter); + elseif(is_string($control)) + $htmlWriter->write($control); + } + + $text = $this->getText(); + if(strlen($text)==0) + $text = $htmlWriter->flush(); + if($this->getTrim()) + $text = trim($text); + + $writer->write($this->translateText($text, $subs)); + } + + /** + * Translates the text with subsititution. + * @param string text for translation + * @param array list of substitutions + * @return string translated text + */ + protected function translateText($text, $subs) + { + $app = $this->getApplication()->getGlobalization(); + + //no translation handler provided + if(($config = $app->getTranslationConfiguration())===null) + return strtr($text, $subs); + + $catalogue = $this->getCatalogue(); + if(empty($catalogue) && isset($config['catalogue'])) + $catalogue = $config['catalogue']; + if (empty($catalogue)) $catalogue='messages'; + Translation::init($catalogue); + + $key = $this->getKey(); + if(!empty($key)) $text = $key; + + //translate it + return Translation::formatter($catalogue)->format($text, + $subs, $catalogue, $this->getCharset()); + } +} + diff --git a/framework/I18N/TTranslateParameter.php b/framework/I18N/TTranslateParameter.php index c54f6ab1..6ac6617c 100644 --- a/framework/I18N/TTranslateParameter.php +++ b/framework/I18N/TTranslateParameter.php @@ -1,119 +1,119 @@ - - * @link http://www.pradosoft.com/ + + * @link http://www.pradosoft.com/ * @copyright Copyright © 2005-2012 PradoSoft - * @license http://www.pradosoft.com/license/ - * @version $Id$ - * @package System.I18N - */ - -/** - * TTranslateParameter component should be used inside the TTranslate component to - * allow parameter substitution. - * - * For example, the strings "{greeting}" and "{name}" will be replace - * with the values of "Hello" and "World", respectively. - * The substitution string must be enclose with "{" and "}". - * The parameters can be further translated by using TTranslate. - * - * - * {greeting} {name}! - * World - * Hello - * - * - * - * Namespace: System.I18N - * - * Properties - * - Key, string, required. - *
Gets or sets the string in TTranslate to substitute. - * - Trim, boolean, - *
Gets or sets an option to trim the contents of the TParam. - * Default is to trim the contents. - * - * @author Xiang Wei Zhuo - * @version v3.0, last update on Friday, 6 January 2006 - * @package System.I18N - */ -class TTranslateParameter extends TControl -{ - /** - * The substitution key. - * @var string - */ - protected $key; - - /** - * To trim or not to trim the contents. - * @var boolean - */ - protected $trim = true; - - - /** - * Get the parameter substitution key. - * @return string substitution key. - */ - public function getKey() - { - if(empty($this->key)) - throw new TException('The Key property must be specified.'); - return $this->key; - } - - /** - * Set the parameter substitution key. - * @param string substitution key. - */ - public function setKey($value) - { - $this->key = $value; - } - - /** - * Set the option to trim the contents. - * @param boolean trim or not. - */ - public function setTrim($value) - { - $this->trim = TPropertyValue::ensureBoolean($value); - } - - /** - * Trim the content or not. - * @return boolean trim or not. - */ - public function getTrim() - { - return $this->trim; - } - - public function getValue() - { - return $this->getViewState('Value', ''); - } - - public function setValue($value) - { - $this->setViewState('Value', $value, ''); - } - - /** - * @return string parameter contents. - */ - public function getParameter() - { - $value = $this->getValue(); - if(strlen($value) > 0) - return $value; - $htmlWriter = Prado::createComponent($this->GetResponse()->getHtmlWriterType(), new TTextWriter()); - $this->renderControl($htmlWriter); - return $this->getTrim() ? - trim($htmlWriter->flush()) : $htmlWriter->flush(); - } -} - + * @license http://www.pradosoft.com/license/ + * @version $Id$ + * @package System.I18N + */ + +/** + * TTranslateParameter component should be used inside the TTranslate component to + * allow parameter substitution. + * + * For example, the strings "{greeting}" and "{name}" will be replace + * with the values of "Hello" and "World", respectively. + * The substitution string must be enclose with "{" and "}". + * The parameters can be further translated by using TTranslate. + * + * + * {greeting} {name}! + * World + * Hello + * + * + * + * Namespace: System.I18N + * + * Properties + * - Key, string, required. + *
Gets or sets the string in TTranslate to substitute. + * - Trim, boolean, + *
Gets or sets an option to trim the contents of the TParam. + * Default is to trim the contents. + * + * @author Xiang Wei Zhuo + * @version v3.0, last update on Friday, 6 January 2006 + * @package System.I18N + */ +class TTranslateParameter extends TControl +{ + /** + * The substitution key. + * @var string + */ + protected $key; + + /** + * To trim or not to trim the contents. + * @var boolean + */ + protected $trim = true; + + + /** + * Get the parameter substitution key. + * @return string substitution key. + */ + public function getKey() + { + if(empty($this->key)) + throw new TException('The Key property must be specified.'); + return $this->key; + } + + /** + * Set the parameter substitution key. + * @param string substitution key. + */ + public function setKey($value) + { + $this->key = $value; + } + + /** + * Set the option to trim the contents. + * @param boolean trim or not. + */ + public function setTrim($value) + { + $this->trim = TPropertyValue::ensureBoolean($value); + } + + /** + * Trim the content or not. + * @return boolean trim or not. + */ + public function getTrim() + { + return $this->trim; + } + + public function getValue() + { + return $this->getViewState('Value', ''); + } + + public function setValue($value) + { + $this->setViewState('Value', $value, ''); + } + + /** + * @return string parameter contents. + */ + public function getParameter() + { + $value = $this->getValue(); + if(strlen($value) > 0) + return $value; + $htmlWriter = Prado::createComponent($this->GetResponse()->getHtmlWriterType(), new TTextWriter()); + $this->renderControl($htmlWriter); + return $this->getTrim() ? + trim($htmlWriter->flush()) : $htmlWriter->flush(); + } +} + diff --git a/framework/I18N/core/ChoiceFormat.php b/framework/I18N/core/ChoiceFormat.php index b8eb69f0..dd8e48f8 100644 --- a/framework/I18N/core/ChoiceFormat.php +++ b/framework/I18N/core/ChoiceFormat.php @@ -1,226 +1,226 @@ - - * @version $Revision: 1.1 $ $Date: 2005/01/11 07:19:39 $ - * @package System.I18N.core - */ - - -/** - * ChoiceFormat class. - * - * ChoiceFormat converts between ranges of numeric values and string - * names for those ranges. - * - * A ChoiceFormat splits the real number line -Inf to +Inf into two or - * more contiguous ranges. Each range is mapped to a string. - * ChoiceFormat is generally used in a MessageFormat for displaying - * grammatically correct plurals such as "There are 2 files." - * - * - * $string = '[0] are no files |[1] is one file |(1,Inf] are {number} files'; - * - * $formatter = new MessageFormat(...); //init for a source - * $translated = $formatter->format($string); - * - * $choice = new ChoiceFormat(); - * echo $choice->format($translated, 0); //shows "are no files" - * - * - * The message/string choices are separated by the pipe "|" followed - * by a set notation of the form - * # [1,2] -- accepts values between 1 and 2, inclusive. - * # (1,2) -- accepts values between 1 and 2, excluding 1 and 2. - * # {1,2,3,4} -- only values defined in the set are accepted. - * # [-Inf,0) -- accepts value greater or equal to negative infinity - * and strictly less than 0 - * Any non-empty combinations of the delimiters of square and round brackets - * are acceptable. - * - * Since version 3.1.2 the following set notation is also possible. - * - * # {n: n % 10 > 1 && n % 10 < 5} -- matches numbers like 2, 3, 4, 22, 23, 24 - * - * Where set is defined by the expression after n:. In particular, the expression - * accepts the following mathematical/logical operators to form a set of logical conditions - * on the value given by n: - * # < -- less than. - * # <= -- less than equals. - * # > -- greater than. - * # >= -- greater than equals. - * # == -- of equal value. - * # % -- modulo, e.g., 1 % 10 equals 1, 11 % 10 equals 1. - * # - -- minus, negative. - * # + -- addition. - * # & -- conditional AND. - * # && -- condition AND with short circuit. - * # | -- conditional OR. - * # || -- conditional OR with short circuit. - * # ! -- negation. - * - * Additional round brackets can also be used to perform grouping. - * - * @author Xiang Wei Zhuo - * @version v1.0, last update on Fri Dec 24 20:46:16 EST 2004 - * @package System.I18N.core - */ -class ChoiceFormat -{ - /** - * The pattern to validate a set notation - * @var string - */ - protected $validate = '/[\(\[\{]|[-Inf\d:\s]+|,|[\+Inf\d\s:\?\-=!><%\|&\(\)]+|[\)\]\}]/ms'; - - /** - * The pattern to parse the formatting string. - * @var string - */ - protected $parse = '/\s*\|?([\(\[\{]([-Inf\d:\s]+,?[\+Inf\d\s:\?\-=!><%\|&\(\)]*)+[\)\]\}])\s*/'; - - /** - * The value for positive infinity. - * @var float - */ - protected $inf; - - - /** - * Constructor. - */ - function __construct() - { - $this->inf = -log(0); - } - - - /** - * Determine if the given number belongs to a given set - * @param float the number to test. - * @param string the set, in set notation. - * @return boolean true if number is in the set, false otherwise. - */ - function isValid($number, $set) - { - $n = preg_match_all($this->validate,$set,$matches,PREG_SET_ORDER); - - if($n < 3) throw new Exception("Invalid set \"{$set}\""); - - if(preg_match('/\{\s*n:([^\}]+)\}/', $set, $def)) - { - return $this->isValidSetNotation($number, $def[1]); - } - - $leftBracket = $matches[0][0]; - $rightBracket = $matches[$n-1][0]; - - $i = 0; - $elements = array(); - foreach($matches as $match) - { - $string = $match[0]; - if($i != 0 && $i != $n-1 && $string !== ',') - { - if($string == '-Inf') - $elements[] = -1*$this->inf; - else if ($string == '+Inf' || $string == 'Inf') - $elements[] = $this->inf; - else - $elements[] = floatval($string); - } - $i++; - } - $total = count($elements); - $number = floatval($number); - - if($leftBracket == '{' && $rightBracket == '}') - return in_array($number, $elements); - - $left = false; - if($leftBracket == '[') - $left = $number >= $elements[0]; - else if ($leftBracket == '(') - $left = $number > $elements[0]; - - $right = false; - if($rightBracket==']') - $right = $number <= $elements[$total-1]; - else if($rightBracket == ')') - $right = $number < $elements[$total-1]; - - if($left && $right) return true; - - return false; - } - - protected function isValidSetNotation($number, $set) - { - $str = '$result = '.str_replace('n', '$number', $set).';'; - try - { - eval($str); - return $result; - } - catch(Exception $e) - { - return false; - } - } - - /** - * Parse a choice string and get a list of sets and a list of strings - * corresponding to the sets. - * @param string the string containing the choices - * @return array array($sets, $strings) - */ - function parse($string) - { - $n = preg_match_all($this->parse,$string,$matches, PREG_OFFSET_CAPTURE); - $sets = array(); - foreach($matches[1] as $match) - $sets[] = $match[0]; - $offset = $matches[0]; - $strings = array(); - for($i = 0; $i < $n; $i++) - { - $len = strlen($offset[$i][0]); - $begin = $i == 0? $len : $offset[$i][1] + $len; - $end = $i == $n-1 ? strlen($string) : $offset[$i+1][1]; - $strings[] = substr($string, $begin, $end - $begin); - } - return array($sets, $strings); - } - - /** - * For the choice string, and a number, find and return the - * string that satisfied the set within the choices. - * @param string the choices string. - * @param float the number to test. - * @return string the choosen string. - */ - public function format($string, $number) - { - list($sets, $strings) = $this->parse($string); - $total = count($sets); - for($i = 0; $i < $total; $i++) - { - if($this->isValid($number, $sets[$i])) - return $strings[$i]; - } - return false; - } -} - -?> + + * @version $Revision: 1.1 $ $Date: 2005/01/11 07:19:39 $ + * @package System.I18N.core + */ + + +/** + * ChoiceFormat class. + * + * ChoiceFormat converts between ranges of numeric values and string + * names for those ranges. + * + * A ChoiceFormat splits the real number line -Inf to +Inf into two or + * more contiguous ranges. Each range is mapped to a string. + * ChoiceFormat is generally used in a MessageFormat for displaying + * grammatically correct plurals such as "There are 2 files." + * + * + * $string = '[0] are no files |[1] is one file |(1,Inf] are {number} files'; + * + * $formatter = new MessageFormat(...); //init for a source + * $translated = $formatter->format($string); + * + * $choice = new ChoiceFormat(); + * echo $choice->format($translated, 0); //shows "are no files" + * + * + * The message/string choices are separated by the pipe "|" followed + * by a set notation of the form + * # [1,2] -- accepts values between 1 and 2, inclusive. + * # (1,2) -- accepts values between 1 and 2, excluding 1 and 2. + * # {1,2,3,4} -- only values defined in the set are accepted. + * # [-Inf,0) -- accepts value greater or equal to negative infinity + * and strictly less than 0 + * Any non-empty combinations of the delimiters of square and round brackets + * are acceptable. + * + * Since version 3.1.2 the following set notation is also possible. + * + * # {n: n % 10 > 1 && n % 10 < 5} -- matches numbers like 2, 3, 4, 22, 23, 24 + * + * Where set is defined by the expression after n:. In particular, the expression + * accepts the following mathematical/logical operators to form a set of logical conditions + * on the value given by n: + * # < -- less than. + * # <= -- less than equals. + * # > -- greater than. + * # >= -- greater than equals. + * # == -- of equal value. + * # % -- modulo, e.g., 1 % 10 equals 1, 11 % 10 equals 1. + * # - -- minus, negative. + * # + -- addition. + * # & -- conditional AND. + * # && -- condition AND with short circuit. + * # | -- conditional OR. + * # || -- conditional OR with short circuit. + * # ! -- negation. + * + * Additional round brackets can also be used to perform grouping. + * + * @author Xiang Wei Zhuo + * @version v1.0, last update on Fri Dec 24 20:46:16 EST 2004 + * @package System.I18N.core + */ +class ChoiceFormat +{ + /** + * The pattern to validate a set notation + * @var string + */ + protected $validate = '/[\(\[\{]|[-Inf\d:\s]+|,|[\+Inf\d\s:\?\-=!><%\|&\(\)]+|[\)\]\}]/ms'; + + /** + * The pattern to parse the formatting string. + * @var string + */ + protected $parse = '/\s*\|?([\(\[\{]([-Inf\d:\s]+,?[\+Inf\d\s:\?\-=!><%\|&\(\)]*)+[\)\]\}])\s*/'; + + /** + * The value for positive infinity. + * @var float + */ + protected $inf; + + + /** + * Constructor. + */ + function __construct() + { + $this->inf = -log(0); + } + + + /** + * Determine if the given number belongs to a given set + * @param float the number to test. + * @param string the set, in set notation. + * @return boolean true if number is in the set, false otherwise. + */ + function isValid($number, $set) + { + $n = preg_match_all($this->validate,$set,$matches,PREG_SET_ORDER); + + if($n < 3) throw new Exception("Invalid set \"{$set}\""); + + if(preg_match('/\{\s*n:([^\}]+)\}/', $set, $def)) + { + return $this->isValidSetNotation($number, $def[1]); + } + + $leftBracket = $matches[0][0]; + $rightBracket = $matches[$n-1][0]; + + $i = 0; + $elements = array(); + foreach($matches as $match) + { + $string = $match[0]; + if($i != 0 && $i != $n-1 && $string !== ',') + { + if($string == '-Inf') + $elements[] = -1*$this->inf; + else if ($string == '+Inf' || $string == 'Inf') + $elements[] = $this->inf; + else + $elements[] = floatval($string); + } + $i++; + } + $total = count($elements); + $number = floatval($number); + + if($leftBracket == '{' && $rightBracket == '}') + return in_array($number, $elements); + + $left = false; + if($leftBracket == '[') + $left = $number >= $elements[0]; + else if ($leftBracket == '(') + $left = $number > $elements[0]; + + $right = false; + if($rightBracket==']') + $right = $number <= $elements[$total-1]; + else if($rightBracket == ')') + $right = $number < $elements[$total-1]; + + if($left && $right) return true; + + return false; + } + + protected function isValidSetNotation($number, $set) + { + $str = '$result = '.str_replace('n', '$number', $set).';'; + try + { + eval($str); + return $result; + } + catch(Exception $e) + { + return false; + } + } + + /** + * Parse a choice string and get a list of sets and a list of strings + * corresponding to the sets. + * @param string the string containing the choices + * @return array array($sets, $strings) + */ + function parse($string) + { + $n = preg_match_all($this->parse,$string,$matches, PREG_OFFSET_CAPTURE); + $sets = array(); + foreach($matches[1] as $match) + $sets[] = $match[0]; + $offset = $matches[0]; + $strings = array(); + for($i = 0; $i < $n; $i++) + { + $len = strlen($offset[$i][0]); + $begin = $i == 0? $len : $offset[$i][1] + $len; + $end = $i == $n-1 ? strlen($string) : $offset[$i+1][1]; + $strings[] = substr($string, $begin, $end - $begin); + } + return array($sets, $strings); + } + + /** + * For the choice string, and a number, find and return the + * string that satisfied the set within the choices. + * @param string the choices string. + * @param float the number to test. + * @return string the choosen string. + */ + public function format($string, $number) + { + list($sets, $strings) = $this->parse($string); + $total = count($sets); + for($i = 0; $i < $total; $i++) + { + if($this->isValid($number, $sets[$i])) + return $strings[$i]; + } + return false; + } +} + +?> diff --git a/framework/I18N/core/CultureInfo.php b/framework/I18N/core/CultureInfo.php index 18aae74d..799ccdb4 100644 --- a/framework/I18N/core/CultureInfo.php +++ b/framework/I18N/core/CultureInfo.php @@ -1,632 +1,632 @@ - - * @version $Id$ - * @package System.I18N.core - */ - -/** - * CultureInfo class. - * - * Represents information about a specific culture including the - * names of the culture, the calendar used, as well as access to - * culture-specific objects that provide methods for common operations, - * such as formatting dates, numbers, and currency. - * - * The CultureInfo class holds culture-specific information, such as the - * associated language, sublanguage, country/region, calendar, and cultural - * conventions. This class also provides access to culture-specific - * instances of DateTimeFormatInfo and NumberFormatInfo. These objects - * contain the information required for culture-specific operations, - * such as formatting dates, numbers and currency. - * - * The culture names follow the format "_", - * where is a lowercase two-letter code derived from ISO 639 - * codes. You can find a full list of the ISO-639 codes at - * http://www.ics.uci.edu/pub/ietf/http/related/iso639.txt - * - * The is an uppercase two-letter code derived from - * ISO 3166. A copy of ISO-3166 can be found at - * http://www.chemie.fu-berlin.de/diverse/doc/ISO_3166.html - * - * For example, Australian English is "en_AU". - * - * @author Xiang Wei Zhuo - * @version $Id$ - * @package System.I18N.core - */ -class CultureInfo -{ - /** - * ICU data filename extension. - * @var string - */ - private $dataFileExt = '.dat'; - - /** - * The ICU data array. - * @var array - */ - private $data = array(); - - /** - * The current culture. - * @var string - */ - private $culture; - - /** - * Directory where the ICU data is stored. - * @var string - */ - private $dataDir; - - /** - * A list of ICU date files loaded. - * @var array - */ - private $dataFiles = array(); - - /** - * The current date time format info. - * @var DateTimeFormatInfo - */ - private $dateTimeFormat; - - /** - * The current number format info. - * @var NumberFormatInfo - */ - private $numberFormat; - - /** - * A list of properties that are accessable/writable. - * @var array - */ - protected $properties = array(); - - /** - * Culture type, all. - * @see getCultures() - * @var int - */ - const ALL = 0; - - /** - * Culture type, neutral. - * @see getCultures() - * @var int - */ - const NEUTRAL = 1; - - /** - * Culture type, specific. - * @see getCultures() - * @var int - */ - const SPECIFIC = 2; - - /** - * Display the culture name. - * @return string the culture name. - * @see getName() - */ - function __toString() - { - return $this->getName(); - } - - - /** - * Allow functions that begins with 'set' to be called directly - * as an attribute/property to retrieve the value. - * @return mixed - */ - function __get($name) - { - $getProperty = 'get'.$name; - if(in_array($getProperty, $this->properties)) - return $this->$getProperty(); - else - throw new Exception('Property '.$name.' does not exists.'); - } - - /** - * Allow functions that begins with 'set' to be called directly - * as an attribute/property to set the value. - */ - function __set($name, $value) - { - $setProperty = 'set'.$name; - if(in_array($setProperty, $this->properties)) - $this->$setProperty($value); - else - throw new Exception('Property '.$name.' can not be set.'); - } - - - /** - * Initializes a new instance of the CultureInfo class based on the - * culture specified by name. E.g. new CultureInfo('en_AU'); - * The culture indentifier must be of the form - * "language_(country/region/variant)". - * @param string a culture name, e.g. "en_AU". - * @return return new CultureInfo. - */ - function __construct($culture='en') - { - $this->properties = get_class_methods($this); - - if(empty($culture)) - $culture = 'en'; - - $this->dataDir = $this->dataDir(); - $this->dataFileExt = $this->fileExt(); - - $this->setCulture($culture); - - $this->loadCultureData('root'); - $this->loadCultureData($culture); - } - - /** - * Get the default directory for the ICU data. - * The default is the "data" directory for this class. - * @return string directory containing the ICU data. - */ - protected static function dataDir() - { - return dirname(__FILE__).'/data/'; - } - - /** - * Get the filename extension for ICU data. Default is ".dat". - * @return string filename extension for ICU data. - */ - protected static function fileExt() - { - return '.dat'; - } - - /** - * Gets the CultureInfo that for this culture string - * @return CultureInfo invariant culture info is "en". - */ - public static function getInstance($culture) - { - static $instances = array(); - if(!isset($instances[$culture])) - $instances[$culture] = new CultureInfo($culture); - return $instances[$culture]; - } - - /** - * Determine if a given culture is valid. Simply checks that the - * culture data exists. - * @param string a culture - * @return boolean true if valid, false otherwise. - */ - public static function validCulture($culture) - { - if(preg_match('/^[_\\w]+$/', $culture)) - return is_file(self::dataDir().$culture.self::fileExt()); - - return false; - } - - /** - * Set the culture for the current instance. The culture indentifier - * must be of the form "_(country/region)". - * @param string culture identifier, e.g. "fr_FR_EURO". - */ - protected function setCulture($culture) - { - if(!empty($culture)) - { - if (!preg_match('/^[_\\w]+$/', $culture)) - throw new Exception('Invalid culture supplied: ' . $culture); - } - - $this->culture = $culture; - } - - /** - * Load the ICU culture data for the specific culture identifier. - * @param string the culture identifier. - */ - protected function loadCultureData($culture) - { - $file_parts = explode('_',$culture); - $current_part = $file_parts[0]; - - $files = array($current_part); - - for($i = 1, $k = count($file_parts); $i < $k; ++$i) - { - $current_part .= '_'.$file_parts[$i]; - $files[] = $current_part; - } - - foreach($files as $file) - { - $filename = $this->dataDir.$file.$this->dataFileExt; - - if(is_file($filename) == false) - throw new Exception('Data file for "'.$file.'" was not found.'); - - if(in_array($filename, $this->dataFiles) === false) - { - array_unshift($this->dataFiles, $file); - - $data = &$this->getData($filename); - $this->data[$file] = &$data; - - if(isset($data['__ALIAS'])) - $this->loadCultureData($data['__ALIAS'][0]); - unset($data); - } - } - } - - /** - * Get the data by unserializing the ICU data from disk. - * The data files are cached in a static variable inside - * this function. - * @param string the ICU data filename - * @return array ICU data - */ - protected function &getData($filename) - { - static $data = array(); - static $files = array(); - - if(!in_array($filename, $files)) - { - $data[$filename] = unserialize(file_get_contents($filename)); - $files[] = $filename; - } - - return $data[$filename]; - } - - /** - * Find the specific ICU data information from the data. - * The path to the specific ICU data is separated with a slash "/". - * E.g. To find the default calendar used by the culture, the path - * "calendar/default" will return the corresponding default calendar. - * Use merge=true to return the ICU including the parent culture. - * E.g. The currency data for a variant, say "en_AU" contains one - * entry, the currency for AUD, the other currency data are stored - * in the "en" data file. Thus to retrieve all the data regarding - * currency for "en_AU", you need to use findInfo("Currencies,true);. - * @param string the data you want to find. - * @param boolean merge the data from its parents. - * @return mixed the specific ICU data. - */ - protected function findInfo($path='/', $merge=false) - { - $result = array(); - foreach($this->dataFiles as $section) - { - $info = $this->searchArray($this->data[$section], $path); - - if($info) - { - if($merge) - $result = array_merge($info,$result); - else - return $info; - } - } - - return $result; - } - - /** - * Search the array for a specific value using a path separated using - * slash "/" separated path. e.g to find $info['hello']['world'], - * the path "hello/world" will return the corresponding value. - * @param array the array for search - * @param string slash "/" separated array path. - * @return mixed the value array using the path - */ - private function searchArray($info, $path='/') - { - $index = explode('/',$path); - - $array = $info; - - for($i = 0, $k = count($index); $i < $k; ++$i) - { - $value = $index[$i]; - if($i < $k-1 && isset($array[$value])) - $array = $array[$value]; - else if ($i == $k-1 && isset($array[$value])) - return $array[$value]; - } - } - - /** - * Gets the culture name in the format - * "_(country/regioncode2)". - * @return string culture name. - */ - function getName() - { - return $this->culture; - } - - /** - * Gets the DateTimeFormatInfo that defines the culturally appropriate - * format of displaying dates and times. - * @return DateTimeFormatInfo date time format information for the culture. - */ - function getDateTimeFormat() - { - if($this->dateTimeFormat === null) - { - $calendar = $this->getCalendar(); - $info = $this->findInfo("calendar/{$calendar}", true); - $this->setDateTimeFormat(new DateTimeFormatInfo($info)); - } - - return $this->dateTimeFormat; - } - - /** - * Set the date time format information. - * @param DateTimeFormatInfo the new date time format info. - */ - function setDateTimeFormat($dateTimeFormat) - { - $this->dateTimeFormat = $dateTimeFormat; - } - - /** - * Gets the default calendar used by the culture, e.g. "gregorian". - * @return string the default calendar. - */ - function getCalendar() - { - $info = $this->findInfo('calendar/default'); - return $info[0]; - } - - /** - * Gets the culture name in the language that the culture is set - * to display. Returns array('Language','Country'); - * 'Country' is omitted if the culture is neutral. - * @return array array with language and country as elements, localized. - */ - function getNativeName() - { - $lang = substr($this->culture,0,2); - $reg = substr($this->culture,3,2); - $language = $this->findInfo("Languages/{$lang}"); - $region = $this->findInfo("Countries/{$reg}"); - if($region) - return $language[0].' ('.$region[0].')'; - else - return $language[0]; - } - - /** - * Gets the culture name in English. - * Returns array('Language','Country'); - * 'Country' is omitted if the culture is neutral. - * @return string language (country), it may locale code string if english name does not exist. - */ - function getEnglishName() - { - $lang = substr($this->culture,0,2); - $reg = substr($this->culture,3,2); - $culture = $this->getInvariantCulture(); - - $language = $culture->findInfo("Languages/{$lang}"); - if(count($language) == 0) - return $this->culture; - - $region = $culture->findInfo("Countries/{$reg}"); - if($region) - return $language[0].' ('.$region[0].')'; - else - return $language[0]; - } - - /** - * Gets the CultureInfo that is culture-independent (invariant). - * Any changes to the invariant culture affects all other - * instances of the invariant culture. - * The invariant culture is assumed to be "en"; - * @return CultureInfo invariant culture info is "en". - */ - static function getInvariantCulture() - { - static $invariant; - if($invariant === null) - $invariant = new CultureInfo(); - return $invariant; - } - - /** - * Gets a value indicating whether the current CultureInfo - * represents a neutral culture. Returns true if the culture - * only contains two characters. - * @return boolean true if culture is neutral, false otherwise. - */ - function getIsNeutralCulture() - { - return strlen($this->culture) == 2; - } - - /** - * Gets the NumberFormatInfo that defines the culturally appropriate - * format of displaying numbers, currency, and percentage. - * @return NumberFormatInfo the number format info for current culture. - */ - function getNumberFormat() - { - if($this->numberFormat === null) - { - $elements = $this->findInfo('NumberElements'); - $patterns = $this->findInfo('NumberPatterns'); - $currencies = $this->getCurrencies(); - $data = array( 'NumberElements'=>$elements, - 'NumberPatterns'=>$patterns, - 'Currencies' => $currencies); - - $this->setNumberFormat(new NumberFormatInfo($data)); - } - return $this->numberFormat; - } - - /** - * Set the number format information. - * @param NumberFormatInfo the new number format info. - */ - function setNumberFormat($numberFormat) - { - $this->numberFormat = $numberFormat; - } - - /** - * Gets the CultureInfo that represents the parent culture of the - * current CultureInfo - * @return CultureInfo parent culture information. - */ - function getParent() - { - if(strlen($this->culture) == 2) - return $this->getInvariantCulture(); - - $lang = substr($this->culture,0,2); - return new CultureInfo($lang); - } - - /** - * Gets the list of supported cultures filtered by the specified - * culture type. This is an EXPENSIVE function, it needs to traverse - * a list of ICU files in the data directory. - * This function can be called statically. - * @param int culture type, CultureInfo::ALL, CultureInfo::NEUTRAL - * or CultureInfo::SPECIFIC. - * @return array list of culture information available. - */ - static function getCultures($type=CultureInfo::ALL) - { - $dataDir = CultureInfo::dataDir(); - $dataExt = CultureInfo::fileExt(); - $dir = dir($dataDir); - - $neutral = array(); - $specific = array(); - - while (false !== ($entry = $dir->read())) - { - if(is_file($dataDir.$entry) - && substr($entry,-4) == $dataExt - && $entry != 'root'.$dataExt) - { - $culture = substr($entry,0,-4); - if(strlen($culture) == 2) - $neutral[] = $culture; - else - $specific[] = $culture; - } - } - $dir->close(); - - switch($type) - { - case CultureInfo::ALL : - $all = array_merge($neutral, $specific); - sort($all); - return $all; - break; - case CultureInfo::NEUTRAL : - return $neutral; - break; - case CultureInfo::SPECIFIC : - return $specific; - break; - } - } - - /** - * Simplify a single element array into its own value. - * E.g. array(0 => array('hello'), 1 => 'world'); - * becomes array(0 => 'hello', 1 => 'world'); - * @param array with single elements arrays - * @return array simplified array. - */ - private function simplify($array) - { - for($i = 0, $k = count($array); $i<$k; ++$i) - { - $key = key($array); - if(is_array($array[$key]) - && count($array[$key]) == 1) - $array[$key] = $array[$key][0]; - next($array); - } - return $array; - } - - /** - * Get a list of countries in the language of the localized version. - * @return array a list of localized country names. - */ - function getCountries() - { - return $this->simplify($this->findInfo('Countries',true)); - } - - /** - * Get a list of currencies in the language of the localized version. - * @return array a list of localized currencies. - */ - function getCurrencies() - { - return $this->findInfo('Currencies',true); - } - - /** - * Get a list of languages in the language of the localized version. - * @return array list of localized language names. - */ - function getLanguages() - { - return $this->simplify($this->findInfo('Languages',true)); - } - - /** - * Get a list of scripts in the language of the localized version. - * @return array list of localized script names. - */ - function getScripts() - { - return $this->simplify($this->findInfo('Scripts',true)); - } - - /** - * Get a list of timezones in the language of the localized version. - * @return array list of localized timezones. - */ - function getTimeZones() - { - return $this->simplify($this->findInfo('zoneStrings',true)); - } -} - + + * @version $Id$ + * @package System.I18N.core + */ + +/** + * CultureInfo class. + * + * Represents information about a specific culture including the + * names of the culture, the calendar used, as well as access to + * culture-specific objects that provide methods for common operations, + * such as formatting dates, numbers, and currency. + * + * The CultureInfo class holds culture-specific information, such as the + * associated language, sublanguage, country/region, calendar, and cultural + * conventions. This class also provides access to culture-specific + * instances of DateTimeFormatInfo and NumberFormatInfo. These objects + * contain the information required for culture-specific operations, + * such as formatting dates, numbers and currency. + * + * The culture names follow the format "_", + * where is a lowercase two-letter code derived from ISO 639 + * codes. You can find a full list of the ISO-639 codes at + * http://www.ics.uci.edu/pub/ietf/http/related/iso639.txt + * + * The is an uppercase two-letter code derived from + * ISO 3166. A copy of ISO-3166 can be found at + * http://www.chemie.fu-berlin.de/diverse/doc/ISO_3166.html + * + * For example, Australian English is "en_AU". + * + * @author Xiang Wei Zhuo + * @version $Id$ + * @package System.I18N.core + */ +class CultureInfo +{ + /** + * ICU data filename extension. + * @var string + */ + private $dataFileExt = '.dat'; + + /** + * The ICU data array. + * @var array + */ + private $data = array(); + + /** + * The current culture. + * @var string + */ + private $culture; + + /** + * Directory where the ICU data is stored. + * @var string + */ + private $dataDir; + + /** + * A list of ICU date files loaded. + * @var array + */ + private $dataFiles = array(); + + /** + * The current date time format info. + * @var DateTimeFormatInfo + */ + private $dateTimeFormat; + + /** + * The current number format info. + * @var NumberFormatInfo + */ + private $numberFormat; + + /** + * A list of properties that are accessable/writable. + * @var array + */ + protected $properties = array(); + + /** + * Culture type, all. + * @see getCultures() + * @var int + */ + const ALL = 0; + + /** + * Culture type, neutral. + * @see getCultures() + * @var int + */ + const NEUTRAL = 1; + + /** + * Culture type, specific. + * @see getCultures() + * @var int + */ + const SPECIFIC = 2; + + /** + * Display the culture name. + * @return string the culture name. + * @see getName() + */ + function __toString() + { + return $this->getName(); + } + + + /** + * Allow functions that begins with 'set' to be called directly + * as an attribute/property to retrieve the value. + * @return mixed + */ + function __get($name) + { + $getProperty = 'get'.$name; + if(in_array($getProperty, $this->properties)) + return $this->$getProperty(); + else + throw new Exception('Property '.$name.' does not exists.'); + } + + /** + * Allow functions that begins with 'set' to be called directly + * as an attribute/property to set the value. + */ + function __set($name, $value) + { + $setProperty = 'set'.$name; + if(in_array($setProperty, $this->properties)) + $this->$setProperty($value); + else + throw new Exception('Property '.$name.' can not be set.'); + } + + + /** + * Initializes a new instance of the CultureInfo class based on the + * culture specified by name. E.g. new CultureInfo('en_AU'); + * The culture indentifier must be of the form + * "language_(country/region/variant)". + * @param string a culture name, e.g. "en_AU". + * @return return new CultureInfo. + */ + function __construct($culture='en') + { + $this->properties = get_class_methods($this); + + if(empty($culture)) + $culture = 'en'; + + $this->dataDir = $this->dataDir(); + $this->dataFileExt = $this->fileExt(); + + $this->setCulture($culture); + + $this->loadCultureData('root'); + $this->loadCultureData($culture); + } + + /** + * Get the default directory for the ICU data. + * The default is the "data" directory for this class. + * @return string directory containing the ICU data. + */ + protected static function dataDir() + { + return dirname(__FILE__).'/data/'; + } + + /** + * Get the filename extension for ICU data. Default is ".dat". + * @return string filename extension for ICU data. + */ + protected static function fileExt() + { + return '.dat'; + } + + /** + * Gets the CultureInfo that for this culture string + * @return CultureInfo invariant culture info is "en". + */ + public static function getInstance($culture) + { + static $instances = array(); + if(!isset($instances[$culture])) + $instances[$culture] = new CultureInfo($culture); + return $instances[$culture]; + } + + /** + * Determine if a given culture is valid. Simply checks that the + * culture data exists. + * @param string a culture + * @return boolean true if valid, false otherwise. + */ + public static function validCulture($culture) + { + if(preg_match('/^[_\\w]+$/', $culture)) + return is_file(self::dataDir().$culture.self::fileExt()); + + return false; + } + + /** + * Set the culture for the current instance. The culture indentifier + * must be of the form "_(country/region)". + * @param string culture identifier, e.g. "fr_FR_EURO". + */ + protected function setCulture($culture) + { + if(!empty($culture)) + { + if (!preg_match('/^[_\\w]+$/', $culture)) + throw new Exception('Invalid culture supplied: ' . $culture); + } + + $this->culture = $culture; + } + + /** + * Load the ICU culture data for the specific culture identifier. + * @param string the culture identifier. + */ + protected function loadCultureData($culture) + { + $file_parts = explode('_',$culture); + $current_part = $file_parts[0]; + + $files = array($current_part); + + for($i = 1, $k = count($file_parts); $i < $k; ++$i) + { + $current_part .= '_'.$file_parts[$i]; + $files[] = $current_part; + } + + foreach($files as $file) + { + $filename = $this->dataDir.$file.$this->dataFileExt; + + if(is_file($filename) == false) + throw new Exception('Data file for "'.$file.'" was not found.'); + + if(in_array($filename, $this->dataFiles) === false) + { + array_unshift($this->dataFiles, $file); + + $data = &$this->getData($filename); + $this->data[$file] = &$data; + + if(isset($data['__ALIAS'])) + $this->loadCultureData($data['__ALIAS'][0]); + unset($data); + } + } + } + + /** + * Get the data by unserializing the ICU data from disk. + * The data files are cached in a static variable inside + * this function. + * @param string the ICU data filename + * @return array ICU data + */ + protected function &getData($filename) + { + static $data = array(); + static $files = array(); + + if(!in_array($filename, $files)) + { + $data[$filename] = unserialize(file_get_contents($filename)); + $files[] = $filename; + } + + return $data[$filename]; + } + + /** + * Find the specific ICU data information from the data. + * The path to the specific ICU data is separated with a slash "/". + * E.g. To find the default calendar used by the culture, the path + * "calendar/default" will return the corresponding default calendar. + * Use merge=true to return the ICU including the parent culture. + * E.g. The currency data for a variant, say "en_AU" contains one + * entry, the currency for AUD, the other currency data are stored + * in the "en" data file. Thus to retrieve all the data regarding + * currency for "en_AU", you need to use findInfo("Currencies,true);. + * @param string the data you want to find. + * @param boolean merge the data from its parents. + * @return mixed the specific ICU data. + */ + protected function findInfo($path='/', $merge=false) + { + $result = array(); + foreach($this->dataFiles as $section) + { + $info = $this->searchArray($this->data[$section], $path); + + if($info) + { + if($merge) + $result = array_merge($info,$result); + else + return $info; + } + } + + return $result; + } + + /** + * Search the array for a specific value using a path separated using + * slash "/" separated path. e.g to find $info['hello']['world'], + * the path "hello/world" will return the corresponding value. + * @param array the array for search + * @param string slash "/" separated array path. + * @return mixed the value array using the path + */ + private function searchArray($info, $path='/') + { + $index = explode('/',$path); + + $array = $info; + + for($i = 0, $k = count($index); $i < $k; ++$i) + { + $value = $index[$i]; + if($i < $k-1 && isset($array[$value])) + $array = $array[$value]; + else if ($i == $k-1 && isset($array[$value])) + return $array[$value]; + } + } + + /** + * Gets the culture name in the format + * "_(country/regioncode2)". + * @return string culture name. + */ + function getName() + { + return $this->culture; + } + + /** + * Gets the DateTimeFormatInfo that defines the culturally appropriate + * format of displaying dates and times. + * @return DateTimeFormatInfo date time format information for the culture. + */ + function getDateTimeFormat() + { + if($this->dateTimeFormat === null) + { + $calendar = $this->getCalendar(); + $info = $this->findInfo("calendar/{$calendar}", true); + $this->setDateTimeFormat(new DateTimeFormatInfo($info)); + } + + return $this->dateTimeFormat; + } + + /** + * Set the date time format information. + * @param DateTimeFormatInfo the new date time format info. + */ + function setDateTimeFormat($dateTimeFormat) + { + $this->dateTimeFormat = $dateTimeFormat; + } + + /** + * Gets the default calendar used by the culture, e.g. "gregorian". + * @return string the default calendar. + */ + function getCalendar() + { + $info = $this->findInfo('calendar/default'); + return $info[0]; + } + + /** + * Gets the culture name in the language that the culture is set + * to display. Returns array('Language','Country'); + * 'Country' is omitted if the culture is neutral. + * @return array array with language and country as elements, localized. + */ + function getNativeName() + { + $lang = substr($this->culture,0,2); + $reg = substr($this->culture,3,2); + $language = $this->findInfo("Languages/{$lang}"); + $region = $this->findInfo("Countries/{$reg}"); + if($region) + return $language[0].' ('.$region[0].')'; + else + return $language[0]; + } + + /** + * Gets the culture name in English. + * Returns array('Language','Country'); + * 'Country' is omitted if the culture is neutral. + * @return string language (country), it may locale code string if english name does not exist. + */ + function getEnglishName() + { + $lang = substr($this->culture,0,2); + $reg = substr($this->culture,3,2); + $culture = $this->getInvariantCulture(); + + $language = $culture->findInfo("Languages/{$lang}"); + if(count($language) == 0) + return $this->culture; + + $region = $culture->findInfo("Countries/{$reg}"); + if($region) + return $language[0].' ('.$region[0].')'; + else + return $language[0]; + } + + /** + * Gets the CultureInfo that is culture-independent (invariant). + * Any changes to the invariant culture affects all other + * instances of the invariant culture. + * The invariant culture is assumed to be "en"; + * @return CultureInfo invariant culture info is "en". + */ + static function getInvariantCulture() + { + static $invariant; + if($invariant === null) + $invariant = new CultureInfo(); + return $invariant; + } + + /** + * Gets a value indicating whether the current CultureInfo + * represents a neutral culture. Returns true if the culture + * only contains two characters. + * @return boolean true if culture is neutral, false otherwise. + */ + function getIsNeutralCulture() + { + return strlen($this->culture) == 2; + } + + /** + * Gets the NumberFormatInfo that defines the culturally appropriate + * format of displaying numbers, currency, and percentage. + * @return NumberFormatInfo the number format info for current culture. + */ + function getNumberFormat() + { + if($this->numberFormat === null) + { + $elements = $this->findInfo('NumberElements'); + $patterns = $this->findInfo('NumberPatterns'); + $currencies = $this->getCurrencies(); + $data = array( 'NumberElements'=>$elements, + 'NumberPatterns'=>$patterns, + 'Currencies' => $currencies); + + $this->setNumberFormat(new NumberFormatInfo($data)); + } + return $this->numberFormat; + } + + /** + * Set the number format information. + * @param NumberFormatInfo the new number format info. + */ + function setNumberFormat($numberFormat) + { + $this->numberFormat = $numberFormat; + } + + /** + * Gets the CultureInfo that represents the parent culture of the + * current CultureInfo + * @return CultureInfo parent culture information. + */ + function getParent() + { + if(strlen($this->culture) == 2) + return $this->getInvariantCulture(); + + $lang = substr($this->culture,0,2); + return new CultureInfo($lang); + } + + /** + * Gets the list of supported cultures filtered by the specified + * culture type. This is an EXPENSIVE function, it needs to traverse + * a list of ICU files in the data directory. + * This function can be called statically. + * @param int culture type, CultureInfo::ALL, CultureInfo::NEUTRAL + * or CultureInfo::SPECIFIC. + * @return array list of culture information available. + */ + static function getCultures($type=CultureInfo::ALL) + { + $dataDir = CultureInfo::dataDir(); + $dataExt = CultureInfo::fileExt(); + $dir = dir($dataDir); + + $neutral = array(); + $specific = array(); + + while (false !== ($entry = $dir->read())) + { + if(is_file($dataDir.$entry) + && substr($entry,-4) == $dataExt + && $entry != 'root'.$dataExt) + { + $culture = substr($entry,0,-4); + if(strlen($culture) == 2) + $neutral[] = $culture; + else + $specific[] = $culture; + } + } + $dir->close(); + + switch($type) + { + case CultureInfo::ALL : + $all = array_merge($neutral, $specific); + sort($all); + return $all; + break; + case CultureInfo::NEUTRAL : + return $neutral; + break; + case CultureInfo::SPECIFIC : + return $specific; + break; + } + } + + /** + * Simplify a single element array into its own value. + * E.g. array(0 => array('hello'), 1 => 'world'); + * becomes array(0 => 'hello', 1 => 'world'); + * @param array with single elements arrays + * @return array simplified array. + */ + private function simplify($array) + { + for($i = 0, $k = count($array); $i<$k; ++$i) + { + $key = key($array); + if(is_array($array[$key]) + && count($array[$key]) == 1) + $array[$key] = $array[$key][0]; + next($array); + } + return $array; + } + + /** + * Get a list of countries in the language of the localized version. + * @return array a list of localized country names. + */ + function getCountries() + { + return $this->simplify($this->findInfo('Countries',true)); + } + + /** + * Get a list of currencies in the language of the localized version. + * @return array a list of localized currencies. + */ + function getCurrencies() + { + return $this->findInfo('Currencies',true); + } + + /** + * Get a list of languages in the language of the localized version. + * @return array list of localized language names. + */ + function getLanguages() + { + return $this->simplify($this->findInfo('Languages',true)); + } + + /** + * Get a list of scripts in the language of the localized version. + * @return array list of localized script names. + */ + function getScripts() + { + return $this->simplify($this->findInfo('Scripts',true)); + } + + /** + * Get a list of timezones in the language of the localized version. + * @return array list of localized timezones. + */ + function getTimeZones() + { + return $this->simplify($this->findInfo('zoneStrings',true)); + } +} + diff --git a/framework/I18N/core/DateFormat.php b/framework/I18N/core/DateFormat.php index 7724a480..27c9d3c6 100644 --- a/framework/I18N/core/DateFormat.php +++ b/framework/I18N/core/DateFormat.php @@ -1,652 +1,652 @@ - - * @version $Revision: 1.8 $ $Date: 2005/12/15 07:14:49 $ - * @package System.I18N.core - */ - -/** - * Get the DateTimeFormatInfo class. - */ -require_once(dirname(__FILE__).'/DateTimeFormatInfo.php'); - -/** - * Get the encoding utilities - */ -require_once(dirname(__FILE__).'/util.php'); - -/** - * DateFormat class. - * - * The DateFormat class allows you to format dates and times with - * predefined styles in a locale-sensitive manner. Formatting times - * with the DateFormat class is similar to formatting dates. - * - * Formatting dates with the DateFormat class is a two-step process. - * First, you create a formatter with the getDateInstance method. - * Second, you invoke the format method, which returns a string containing - * the formatted date. - * - * DateTime values are formatted using standard or custom patterns stored - * in the properties of a DateTimeFormatInfo. - * - * @author Xiang Wei Zhuo - * @version v1.0, last update on Sat Dec 04 14:10:49 EST 2004 - * @package System.I18N.core - */ -class DateFormat -{ - /** - * A list of tokens and their function call. - * @var array - */ - protected $tokens = array( - 'G'=>'Era', - 'y'=>'Year', - 'M'=>'Month', - 'd'=>'Day', - 'h'=>'Hour12', - 'H'=>'Hour24', - 'm'=>'Minutes', - 's'=>'Seconds', - 'E'=>'DayInWeek', - 'D'=>'DayInYear', - 'F'=>'DayInMonth', - 'w'=>'WeekInYear', - 'W'=>'WeekInMonth', - 'a'=>'AMPM', - 'k'=>'HourInDay', - 'K'=>'HourInAMPM', - 'z'=>'TimeZone' - ); - - /** - * A list of methods, to be used by the token function calls. - * @var array - */ - protected $methods = array(); - - /** - * The DateTimeFormatInfo, containing culture specific patterns and names. - * @var DateTimeFormatInfo - */ - protected $formatInfo; - - /** - * Initialize a new DateFormat. - * @param mixed either, null, a CultureInfo instance, - * a DateTimeFormatInfo instance, or a locale. - * @return DateFormat instance - */ - function __construct($formatInfo=null) - { - if($formatInfo === null) - $this->formatInfo = DateTimeFormatInfo::getInvariantInfo(); - else if($formatInfo instanceof CultureInfo) - $this->formatInfo = $formatInfo->DateTimeFormat; - else if($formatInfo instanceof DateTimeFormatInfo) - $this->formatInfo = $formatInfo; - else - $this->formatInfo = DateTimeFormatInfo::getInstance($formatInfo); - - $this->methods = get_class_methods($this); - } - - /** - * Format a date according to the pattern. - * @param mixed the time as integer or string in strtotime format. - * @return string formatted date time. - */ - public function format($time, $pattern='F', $charset='UTF-8') - { - if (is_numeric($time)) //assumes unix epoch - $time = floatval($time); - else if(is_string($time)) - $time = @strtotime($time); - - if($pattern === null) - $pattern = 'F'; - - $s = Prado::createComponent('System.Util.TDateTimeStamp'); - - $date = $s->getDate($time); - - $pattern = $this->getPattern($pattern); - - $tokens = $this->getTokens($pattern); - - for($i = 0, $k = count($tokens); $i<$k; ++$i) - { - $pattern = $tokens[$i]; - if($pattern{0} == "'" - && $pattern{strlen($pattern)-1} == "'") - { - $sub = preg_replace('/(^\')|(\'$)/','',$pattern); - $tokens[$i] = str_replace('``````','\'',$sub); - } - else if($pattern == '``````') - { - $tokens[$i] = '\''; - } - else - { - $function = $this->getFunctionName($pattern); - if($function != null) - { - $fName = 'get'.$function; - if(in_array($fName, $this->methods)) - { - $rs = $this->$fName($date, $pattern); - $tokens[$i] = $rs; - } - else - throw new - Exception('function '.$function.' not found.'); - } - } - } - - return I18N_toEncoding(implode('',$tokens), $charset); - } - - /** - * For a particular token, get the corresponding function to call. - * @param string token - * @return mixed the function if good token, null otherwise. - */ - protected function getFunctionName($token) - { - if(isset($this->tokens[$token{0}])) - return $this->tokens[$token{0}]; - } - - /** - * Get the pattern from DateTimeFormatInfo or some predefined patterns. - * If the $pattern parameter is an array of 2 element, it will assume - * that the first element is the date, and second the time - * and try to find an appropriate pattern and apply - * DateTimeFormatInfo::formatDateTime - * See the tutorial documentation for futher details on the patterns. - * @param mixed a pattern. - * @return string a pattern. - * @see DateTimeFormatInfo::formatDateTime() - */ - protected function getPattern($pattern) - { - if(is_array($pattern) && count($pattern) == 2) - { - return $this->formatInfo->formatDateTime( - $this->getPattern($pattern[0]), - $this->getPattern($pattern[1])); - } - - switch($pattern) - { - case 'd': - return $this->formatInfo->ShortDatePattern; - break; - case 'D': - return $this->formatInfo->LongDatePattern; - break; - case 'p': - return $this->formatInfo->MediumDatePattern; - break; - case 'P': - return $this->formatInfo->FullDatePattern; - break; - case 't': - return $this->formatInfo->ShortTimePattern; - break; - case 'T': - return $this->formatInfo->LongTimePattern; - break; - case 'q': - return $this->formatInfo->MediumTimePattern; - break; - case 'Q': - return $this->formatInfo->FullTimePattern; - break; - case 'f': - return $this->formatInfo->formatDateTime( - $this->formatInfo->LongDatePattern, - $this->formatInfo->ShortTimePattern); - break; - case 'F': - return $this->formatInfo->formatDateTime( - $this->formatInfo->LongDatePattern, - $this->formatInfo->LongTimePattern); - break; - case 'g': - return $this->formatInfo->formatDateTime( - $this->formatInfo->ShortDatePattern, - $this->formatInfo->ShortTimePattern); - break; - case 'G': - return $this->formatInfo->formatDateTime( - $this->formatInfo->ShortDatePattern, - $this->formatInfo->LongTimePattern); - break; - case 'M': - case 'm': - return 'MMMM dd'; - break; - case 'R': - case 'r': - return 'EEE, dd MMM yyyy HH:mm:ss'; - break; - case 's': - return 'yyyy-MM-ddTHH:mm:ss'; - break; - case 'u': - return 'yyyy-MM-dd HH:mm:ss z'; - break; - case 'U': - return 'EEEE dd MMMM yyyy HH:mm:ss'; - break; - case 'Y': - case 'y': - return 'yyyy MMMM'; - break; - default : - return $pattern; - } - } - - /** - * Tokenize the pattern. The tokens are delimited by group of - * similar characters, e.g. 'aabb' will form 2 tokens of 'aa' and 'bb'. - * Any substrings, starting and ending with a single quote (') - * will be treated as a single token. - * @param string pattern. - * @return array string tokens in an array. - */ - protected function getTokens($pattern) - { - $char = null; - $tokens = array(); - $token = null; - - $text = false; - $pattern = preg_replace("/''/", '``````', $pattern); - - for($i = 0; $i < strlen($pattern); $i++) - { - if($char==null || $pattern{$i} == $char || $text) - { - $token .= $pattern{$i}; - } - else - { - $tokens[] = str_replace("","'",$token); - $token = $pattern{$i}; - } - - if($pattern{$i} == "'" && $text == false) - $text = true; - else if($text && $pattern{$i} == "'" && $char == "'") - $text = true; - else if($text && $char != "'" && $pattern{$i} == "'") - $text = false; - - $char = $pattern{$i}; - - } - $tokens[] = $token; - return $tokens; - } - - /** - * Get the year. - * "yy" will return the last two digits of year. - * "yyyy" will return the full integer year. - * @param array getdate format. - * @param string a pattern. - * @return string year - */ - protected function getYear($date, $pattern='yyyy') - { - $year = $date['year']; - switch($pattern) - { - case 'yy': - return substr($year,2); - case 'yyyy': - return $year; - default: - throw new Exception('The pattern for year is either "yy" or "yyyy".'); - } - } - - /** - * Get the month. - * "M" will return integer 1 through 12 - * "MM" will return the narrow month name, e.g. "J" - * "MMM" will return the abrreviated month name, e.g. "Jan" - * "MMMM" will return the month name, e.g. "January" - * @param array getdate format. - * @param string a pattern. - * @return string month name - */ - protected function getMonth($date, $pattern='M') - { - $month = $date['mon']; - - switch($pattern) - { - case 'M': - return $month; - case 'MM': - return str_pad($month, 2,'0',STR_PAD_LEFT); - case 'MMM': - return $this->formatInfo->AbbreviatedMonthNames[$month-1]; - break; - case 'MMMM': - return $this->formatInfo->MonthNames[$month-1]; - default: - throw new Exception('The pattern for month '. - 'is "M", "MM", "MMM", or "MMMM".'); - } - } - - /** - * Get the day of the week. - * "E" will return integer 0 (for Sunday) through 6 (for Saturday). - * "EE" will return the narrow day of the week, e.g. "M" - * "EEE" will return the abrreviated day of the week, e.g. "Mon" - * "EEEE" will return the day of the week, e.g. "Monday" - * @param array getdate format. - * @param string a pattern. - * @return string day of the week. - */ - protected function getDayInWeek($date, $pattern='EEEE') - { - $day = $date['wday']; - - switch($pattern) - { - case 'E': - return $day; - break; - case 'EE': - return $this->formatInfo->NarrowDayNames[$day]; - case 'EEE': - return $this->formatInfo->AbbreviatedDayNames[$day]; - break; - case 'EEEE': - return $this->formatInfo->DayNames[$day]; - break; - default: - throw new Exception('The pattern for day of the week '. - 'is "E", "EE", "EEE", or "EEEE".'); - } - } - - /** - * Get the day of the month. - * "d" for non-padding, "dd" will always return 2 characters. - * @param array getdate format. - * @param string a pattern. - * @return string day of the month - */ - protected function getDay($date, $pattern='d') - { - $day = $date['mday']; - - switch($pattern) - { - case 'd': - return $day; - case 'dd': - return str_pad($day, 2,'0',STR_PAD_LEFT); - default: - throw new Exception('The pattern for day of '. - 'the month is "d" or "dd".'); - } - } - - - /** - * Get the era. i.e. in gregorian, year > 0 is AD, else BC. - * @todo How to support multiple Eras?, e.g. Japanese. - * @param array getdate format. - * @param string a pattern. - * @return string era - */ - protected function getEra($date, $pattern='G') - { - - if($pattern != 'G') - throw new Exception('The pattern for era is "G".'); - - $year = $date['year']; - if($year > 0) - return $this->formatInfo->getEra(1); - else - return $this->formatInfo->getEra(0); - } - - /** - * Get the hours in 24 hour format, i.e. [0-23]. - * "H" for non-padding, "HH" will always return 2 characters. - * @param array getdate format. - * @param string a pattern. - * @return string hours in 24 hour format. - */ - protected function getHour24($date, $pattern='H') - { - $hour = $date['hours']; - - switch($pattern) - { - case 'H': - return $hour; - case 'HH': - return str_pad($hour, 2,'0',STR_PAD_LEFT); - default: - throw new Exception('The pattern for 24 hour '. - 'format is "H" or "HH".'); - } - } - - /** - * Get the AM/PM designator, 12 noon is PM, 12 midnight is AM. - * @param array getdate format. - * @param string a pattern. - * @return string AM or PM designator - */ - protected function getAMPM($date, $pattern='a') - { - if($pattern != 'a') - throw new Exception('The pattern for AM/PM marker is "a".'); - - $hour = $date['hours']; - $ampm = (int)($hour/12); - return $this->formatInfo->AMPMMarkers[$ampm]; - } - - /** - * Get the hours in 12 hour format. - * "h" for non-padding, "hh" will always return 2 characters. - * @param array getdate format. - * @param string a pattern. - * @return string hours in 12 hour format. - */ - protected function getHour12($date, $pattern='h') - { - $hour = $date['hours']; - $hour = ($hour==12|$hour==0)?12:($hour)%12; - - switch($pattern) - { - case 'h': - return $hour; - case 'hh': - return str_pad($hour, 2,'0',STR_PAD_LEFT); - default: - throw new Exception('The pattern for 24 hour '. - 'format is "H" or "HH".'); - } - } - - /** - * Get the minutes. - * "m" for non-padding, "mm" will always return 2 characters. - * @param array getdate format. - * @param string a pattern. - * @return string minutes. - */ - protected function getMinutes($date, $pattern='m') - { - $minutes = $date['minutes']; - - switch($pattern) - { - case 'm': - return $minutes; - case 'mm': - return str_pad($minutes, 2,'0',STR_PAD_LEFT); - default: - throw new Exception('The pattern for minutes is "m" or "mm".'); - } - } - - /** - * Get the seconds. - * "s" for non-padding, "ss" will always return 2 characters. - * @param array getdate format. - * @param string a pattern. - * @return string seconds - */ - protected function getSeconds($date, $pattern='s') - { - $seconds = $date['seconds']; - - switch($pattern) - { - case 's': - return $seconds; - case 'ss': - return str_pad($seconds, 2,'0',STR_PAD_LEFT); - default: - throw new Exception('The pattern for seconds is "s" or "ss".'); - } - } - - /** - * Get the timezone from the server machine. - * @todo How to get the timezone for a different region? - * @param array getdate format. - * @param string a pattern. - * @return string time zone - */ - protected function getTimeZone($date, $pattern='z') - { - if($pattern != 'z') - throw new Exception('The pattern for time zone is "z".'); - - return @date('T', @mktime($date['hours'], $date['minutes'], $date['seconds'], $date['mon'], $date['mday'], $date['year'])); - } - - /** - * Get the day in the year, e.g. [1-366] - * @param array getdate format. - * @param string a pattern. - * @return int hours in AM/PM format. - */ - protected function getDayInYear($date, $pattern='D') - { - if($pattern != 'D') - throw new Exception('The pattern for day in year is "D".'); - - return $date['yday']; - } - - /** - * Get day in the month. - * @param array getdate format. - * @param string a pattern. - * @return int day in month - */ - protected function getDayInMonth($date, $pattern='FF') - { - switch ($pattern) { - case 'F': - return @date('j', @mktime(0, 0, 0, $date['mon'], $date['mday'], $date['year'])); - break; - case 'FF': - return @date('d', @mktime(0, 0, 0, $date['mon'], $date['mday'], $date['year'])); - break; - default: - throw new Exception('The pattern for day in month is "F" or "FF".'); - } - } - - /** - * Get the week in the year. - * @param array getdate format. - * @param string a pattern. - * @return int week in year - */ - protected function getWeekInYear($date, $pattern='w') - { - if($pattern != 'w') - throw new Exception('The pattern for week in year is "w".'); - - return @date('W', @mktime(0, 0, 0, $date['mon'], $date['mday'], $date['year'])); - } - - /** - * Get week in the month. - * @param array getdate format. - * @return int week in month - */ - protected function getWeekInMonth($date, $pattern='W') - { - if($pattern != 'W') - throw new Exception('The pattern for week in month is "W".'); - - return @date('W', @mktime(0, 0, 0, $date['mon'], $date['mday'], $date['year'])) - date('W', mktime(0, 0, 0, $date['mon'], 1, $date['year'])); - } - - /** - * Get the hours [1-24]. - * @param array getdate format. - * @param string a pattern. - * @return int hours [1-24] - */ - protected function getHourInDay($date, $pattern='k') - { - if($pattern != 'k') - throw new Exception('The pattern for hour in day is "k".'); - - return $date['hours']+1; - } - - /** - * Get the hours in AM/PM format, e.g [1-12] - * @param array getdate format. - * @param string a pattern. - * @return int hours in AM/PM format. - */ - protected function getHourInAMPM($date, $pattern='K') - { - if($pattern != 'K') - throw new Exception('The pattern for hour in AM/PM is "K".'); - - return ($date['hours']+1)%12; - } - -} - -?> + + * @version $Revision: 1.8 $ $Date: 2005/12/15 07:14:49 $ + * @package System.I18N.core + */ + +/** + * Get the DateTimeFormatInfo class. + */ +require_once(dirname(__FILE__).'/DateTimeFormatInfo.php'); + +/** + * Get the encoding utilities + */ +require_once(dirname(__FILE__).'/util.php'); + +/** + * DateFormat class. + * + * The DateFormat class allows you to format dates and times with + * predefined styles in a locale-sensitive manner. Formatting times + * with the DateFormat class is similar to formatting dates. + * + * Formatting dates with the DateFormat class is a two-step process. + * First, you create a formatter with the getDateInstance method. + * Second, you invoke the format method, which returns a string containing + * the formatted date. + * + * DateTime values are formatted using standard or custom patterns stored + * in the properties of a DateTimeFormatInfo. + * + * @author Xiang Wei Zhuo + * @version v1.0, last update on Sat Dec 04 14:10:49 EST 2004 + * @package System.I18N.core + */ +class DateFormat +{ + /** + * A list of tokens and their function call. + * @var array + */ + protected $tokens = array( + 'G'=>'Era', + 'y'=>'Year', + 'M'=>'Month', + 'd'=>'Day', + 'h'=>'Hour12', + 'H'=>'Hour24', + 'm'=>'Minutes', + 's'=>'Seconds', + 'E'=>'DayInWeek', + 'D'=>'DayInYear', + 'F'=>'DayInMonth', + 'w'=>'WeekInYear', + 'W'=>'WeekInMonth', + 'a'=>'AMPM', + 'k'=>'HourInDay', + 'K'=>'HourInAMPM', + 'z'=>'TimeZone' + ); + + /** + * A list of methods, to be used by the token function calls. + * @var array + */ + protected $methods = array(); + + /** + * The DateTimeFormatInfo, containing culture specific patterns and names. + * @var DateTimeFormatInfo + */ + protected $formatInfo; + + /** + * Initialize a new DateFormat. + * @param mixed either, null, a CultureInfo instance, + * a DateTimeFormatInfo instance, or a locale. + * @return DateFormat instance + */ + function __construct($formatInfo=null) + { + if($formatInfo === null) + $this->formatInfo = DateTimeFormatInfo::getInvariantInfo(); + else if($formatInfo instanceof CultureInfo) + $this->formatInfo = $formatInfo->DateTimeFormat; + else if($formatInfo instanceof DateTimeFormatInfo) + $this->formatInfo = $formatInfo; + else + $this->formatInfo = DateTimeFormatInfo::getInstance($formatInfo); + + $this->methods = get_class_methods($this); + } + + /** + * Format a date according to the pattern. + * @param mixed the time as integer or string in strtotime format. + * @return string formatted date time. + */ + public function format($time, $pattern='F', $charset='UTF-8') + { + if (is_numeric($time)) //assumes unix epoch + $time = floatval($time); + else if(is_string($time)) + $time = @strtotime($time); + + if($pattern === null) + $pattern = 'F'; + + $s = Prado::createComponent('System.Util.TDateTimeStamp'); + + $date = $s->getDate($time); + + $pattern = $this->getPattern($pattern); + + $tokens = $this->getTokens($pattern); + + for($i = 0, $k = count($tokens); $i<$k; ++$i) + { + $pattern = $tokens[$i]; + if($pattern{0} == "'" + && $pattern{strlen($pattern)-1} == "'") + { + $sub = preg_replace('/(^\')|(\'$)/','',$pattern); + $tokens[$i] = str_replace('``````','\'',$sub); + } + else if($pattern == '``````') + { + $tokens[$i] = '\''; + } + else + { + $function = $this->getFunctionName($pattern); + if($function != null) + { + $fName = 'get'.$function; + if(in_array($fName, $this->methods)) + { + $rs = $this->$fName($date, $pattern); + $tokens[$i] = $rs; + } + else + throw new + Exception('function '.$function.' not found.'); + } + } + } + + return I18N_toEncoding(implode('',$tokens), $charset); + } + + /** + * For a particular token, get the corresponding function to call. + * @param string token + * @return mixed the function if good token, null otherwise. + */ + protected function getFunctionName($token) + { + if(isset($this->tokens[$token{0}])) + return $this->tokens[$token{0}]; + } + + /** + * Get the pattern from DateTimeFormatInfo or some predefined patterns. + * If the $pattern parameter is an array of 2 element, it will assume + * that the first element is the date, and second the time + * and try to find an appropriate pattern and apply + * DateTimeFormatInfo::formatDateTime + * See the tutorial documentation for futher details on the patterns. + * @param mixed a pattern. + * @return string a pattern. + * @see DateTimeFormatInfo::formatDateTime() + */ + protected function getPattern($pattern) + { + if(is_array($pattern) && count($pattern) == 2) + { + return $this->formatInfo->formatDateTime( + $this->getPattern($pattern[0]), + $this->getPattern($pattern[1])); + } + + switch($pattern) + { + case 'd': + return $this->formatInfo->ShortDatePattern; + break; + case 'D': + return $this->formatInfo->LongDatePattern; + break; + case 'p': + return $this->formatInfo->MediumDatePattern; + break; + case 'P': + return $this->formatInfo->FullDatePattern; + break; + case 't': + return $this->formatInfo->ShortTimePattern; + break; + case 'T': + return $this->formatInfo->LongTimePattern; + break; + case 'q': + return $this->formatInfo->MediumTimePattern; + break; + case 'Q': + return $this->formatInfo->FullTimePattern; + break; + case 'f': + return $this->formatInfo->formatDateTime( + $this->formatInfo->LongDatePattern, + $this->formatInfo->ShortTimePattern); + break; + case 'F': + return $this->formatInfo->formatDateTime( + $this->formatInfo->LongDatePattern, + $this->formatInfo->LongTimePattern); + break; + case 'g': + return $this->formatInfo->formatDateTime( + $this->formatInfo->ShortDatePattern, + $this->formatInfo->ShortTimePattern); + break; + case 'G': + return $this->formatInfo->formatDateTime( + $this->formatInfo->ShortDatePattern, + $this->formatInfo->LongTimePattern); + break; + case 'M': + case 'm': + return 'MMMM dd'; + break; + case 'R': + case 'r': + return 'EEE, dd MMM yyyy HH:mm:ss'; + break; + case 's': + return 'yyyy-MM-ddTHH:mm:ss'; + break; + case 'u': + return 'yyyy-MM-dd HH:mm:ss z'; + break; + case 'U': + return 'EEEE dd MMMM yyyy HH:mm:ss'; + break; + case 'Y': + case 'y': + return 'yyyy MMMM'; + break; + default : + return $pattern; + } + } + + /** + * Tokenize the pattern. The tokens are delimited by group of + * similar characters, e.g. 'aabb' will form 2 tokens of 'aa' and 'bb'. + * Any substrings, starting and ending with a single quote (') + * will be treated as a single token. + * @param string pattern. + * @return array string tokens in an array. + */ + protected function getTokens($pattern) + { + $char = null; + $tokens = array(); + $token = null; + + $text = false; + $pattern = preg_replace("/''/", '``````', $pattern); + + for($i = 0; $i < strlen($pattern); $i++) + { + if($char==null || $pattern{$i} == $char || $text) + { + $token .= $pattern{$i}; + } + else + { + $tokens[] = str_replace("","'",$token); + $token = $pattern{$i}; + } + + if($pattern{$i} == "'" && $text == false) + $text = true; + else if($text && $pattern{$i} == "'" && $char == "'") + $text = true; + else if($text && $char != "'" && $pattern{$i} == "'") + $text = false; + + $char = $pattern{$i}; + + } + $tokens[] = $token; + return $tokens; + } + + /** + * Get the year. + * "yy" will return the last two digits of year. + * "yyyy" will return the full integer year. + * @param array getdate format. + * @param string a pattern. + * @return string year + */ + protected function getYear($date, $pattern='yyyy') + { + $year = $date['year']; + switch($pattern) + { + case 'yy': + return substr($year,2); + case 'yyyy': + return $year; + default: + throw new Exception('The pattern for year is either "yy" or "yyyy".'); + } + } + + /** + * Get the month. + * "M" will return integer 1 through 12 + * "MM" will return the narrow month name, e.g. "J" + * "MMM" will return the abrreviated month name, e.g. "Jan" + * "MMMM" will return the month name, e.g. "January" + * @param array getdate format. + * @param string a pattern. + * @return string month name + */ + protected function getMonth($date, $pattern='M') + { + $month = $date['mon']; + + switch($pattern) + { + case 'M': + return $month; + case 'MM': + return str_pad($month, 2,'0',STR_PAD_LEFT); + case 'MMM': + return $this->formatInfo->AbbreviatedMonthNames[$month-1]; + break; + case 'MMMM': + return $this->formatInfo->MonthNames[$month-1]; + default: + throw new Exception('The pattern for month '. + 'is "M", "MM", "MMM", or "MMMM".'); + } + } + + /** + * Get the day of the week. + * "E" will return integer 0 (for Sunday) through 6 (for Saturday). + * "EE" will return the narrow day of the week, e.g. "M" + * "EEE" will return the abrreviated day of the week, e.g. "Mon" + * "EEEE" will return the day of the week, e.g. "Monday" + * @param array getdate format. + * @param string a pattern. + * @return string day of the week. + */ + protected function getDayInWeek($date, $pattern='EEEE') + { + $day = $date['wday']; + + switch($pattern) + { + case 'E': + return $day; + break; + case 'EE': + return $this->formatInfo->NarrowDayNames[$day]; + case 'EEE': + return $this->formatInfo->AbbreviatedDayNames[$day]; + break; + case 'EEEE': + return $this->formatInfo->DayNames[$day]; + break; + default: + throw new Exception('The pattern for day of the week '. + 'is "E", "EE", "EEE", or "EEEE".'); + } + } + + /** + * Get the day of the month. + * "d" for non-padding, "dd" will always return 2 characters. + * @param array getdate format. + * @param string a pattern. + * @return string day of the month + */ + protected function getDay($date, $pattern='d') + { + $day = $date['mday']; + + switch($pattern) + { + case 'd': + return $day; + case 'dd': + return str_pad($day, 2,'0',STR_PAD_LEFT); + default: + throw new Exception('The pattern for day of '. + 'the month is "d" or "dd".'); + } + } + + + /** + * Get the era. i.e. in gregorian, year > 0 is AD, else BC. + * @todo How to support multiple Eras?, e.g. Japanese. + * @param array getdate format. + * @param string a pattern. + * @return string era + */ + protected function getEra($date, $pattern='G') + { + + if($pattern != 'G') + throw new Exception('The pattern for era is "G".'); + + $year = $date['year']; + if($year > 0) + return $this->formatInfo->getEra(1); + else + return $this->formatInfo->getEra(0); + } + + /** + * Get the hours in 24 hour format, i.e. [0-23]. + * "H" for non-padding, "HH" will always return 2 characters. + * @param array getdate format. + * @param string a pattern. + * @return string hours in 24 hour format. + */ + protected function getHour24($date, $pattern='H') + { + $hour = $date['hours']; + + switch($pattern) + { + case 'H': + return $hour; + case 'HH': + return str_pad($hour, 2,'0',STR_PAD_LEFT); + default: + throw new Exception('The pattern for 24 hour '. + 'format is "H" or "HH".'); + } + } + + /** + * Get the AM/PM designator, 12 noon is PM, 12 midnight is AM. + * @param array getdate format. + * @param string a pattern. + * @return string AM or PM designator + */ + protected function getAMPM($date, $pattern='a') + { + if($pattern != 'a') + throw new Exception('The pattern for AM/PM marker is "a".'); + + $hour = $date['hours']; + $ampm = (int)($hour/12); + return $this->formatInfo->AMPMMarkers[$ampm]; + } + + /** + * Get the hours in 12 hour format. + * "h" for non-padding, "hh" will always return 2 characters. + * @param array getdate format. + * @param string a pattern. + * @return string hours in 12 hour format. + */ + protected function getHour12($date, $pattern='h') + { + $hour = $date['hours']; + $hour = ($hour==12|$hour==0)?12:($hour)%12; + + switch($pattern) + { + case 'h': + return $hour; + case 'hh': + return str_pad($hour, 2,'0',STR_PAD_LEFT); + default: + throw new Exception('The pattern for 24 hour '. + 'format is "H" or "HH".'); + } + } + + /** + * Get the minutes. + * "m" for non-padding, "mm" will always return 2 characters. + * @param array getdate format. + * @param string a pattern. + * @return string minutes. + */ + protected function getMinutes($date, $pattern='m') + { + $minutes = $date['minutes']; + + switch($pattern) + { + case 'm': + return $minutes; + case 'mm': + return str_pad($minutes, 2,'0',STR_PAD_LEFT); + default: + throw new Exception('The pattern for minutes is "m" or "mm".'); + } + } + + /** + * Get the seconds. + * "s" for non-padding, "ss" will always return 2 characters. + * @param array getdate format. + * @param string a pattern. + * @return string seconds + */ + protected function getSeconds($date, $pattern='s') + { + $seconds = $date['seconds']; + + switch($pattern) + { + case 's': + return $seconds; + case 'ss': + return str_pad($seconds, 2,'0',STR_PAD_LEFT); + default: + throw new Exception('The pattern for seconds is "s" or "ss".'); + } + } + + /** + * Get the timezone from the server machine. + * @todo How to get the timezone for a different region? + * @param array getdate format. + * @param string a pattern. + * @return string time zone + */ + protected function getTimeZone($date, $pattern='z') + { + if($pattern != 'z') + throw new Exception('The pattern for time zone is "z".'); + + return @date('T', @mktime($date['hours'], $date['minutes'], $date['seconds'], $date['mon'], $date['mday'], $date['year'])); + } + + /** + * Get the day in the year, e.g. [1-366] + * @param array getdate format. + * @param string a pattern. + * @return int hours in AM/PM format. + */ + protected function getDayInYear($date, $pattern='D') + { + if($pattern != 'D') + throw new Exception('The pattern for day in year is "D".'); + + return $date['yday']; + } + + /** + * Get day in the month. + * @param array getdate format. + * @param string a pattern. + * @return int day in month + */ + protected function getDayInMonth($date, $pattern='FF') + { + switch ($pattern) { + case 'F': + return @date('j', @mktime(0, 0, 0, $date['mon'], $date['mday'], $date['year'])); + break; + case 'FF': + return @date('d', @mktime(0, 0, 0, $date['mon'], $date['mday'], $date['year'])); + break; + default: + throw new Exception('The pattern for day in month is "F" or "FF".'); + } + } + + /** + * Get the week in the year. + * @param array getdate format. + * @param string a pattern. + * @return int week in year + */ + protected function getWeekInYear($date, $pattern='w') + { + if($pattern != 'w') + throw new Exception('The pattern for week in year is "w".'); + + return @date('W', @mktime(0, 0, 0, $date['mon'], $date['mday'], $date['year'])); + } + + /** + * Get week in the month. + * @param array getdate format. + * @return int week in month + */ + protected function getWeekInMonth($date, $pattern='W') + { + if($pattern != 'W') + throw new Exception('The pattern for week in month is "W".'); + + return @date('W', @mktime(0, 0, 0, $date['mon'], $date['mday'], $date['year'])) - date('W', mktime(0, 0, 0, $date['mon'], 1, $date['year'])); + } + + /** + * Get the hours [1-24]. + * @param array getdate format. + * @param string a pattern. + * @return int hours [1-24] + */ + protected function getHourInDay($date, $pattern='k') + { + if($pattern != 'k') + throw new Exception('The pattern for hour in day is "k".'); + + return $date['hours']+1; + } + + /** + * Get the hours in AM/PM format, e.g [1-12] + * @param array getdate format. + * @param string a pattern. + * @return int hours in AM/PM format. + */ + protected function getHourInAMPM($date, $pattern='K') + { + if($pattern != 'K') + throw new Exception('The pattern for hour in AM/PM is "K".'); + + return ($date['hours']+1)%12; + } + +} + +?> diff --git a/framework/I18N/core/DateTimeFormatInfo.php b/framework/I18N/core/DateTimeFormatInfo.php index aebd094a..d4deee1f 100644 --- a/framework/I18N/core/DateTimeFormatInfo.php +++ b/framework/I18N/core/DateTimeFormatInfo.php @@ -1,516 +1,516 @@ - - * @version $Revision: 1.2 $ $Date: 2005/01/05 03:15:14 $ - * @package System.I18N.core - */ - -/** - * Get the CultureInfo class. - */ -require_once(dirname(__FILE__).'/CultureInfo.php'); - - -/** - * Defines how DateTime values are formatted and displayed, depending - * on the culture. - * - * This class contains information, such as date patterns, time patterns, - * and AM/PM designators. - * - * To create a DateTimeFormatInfo for a specific culture, create a - * CultureInfo for that culture and retrieve the CultureInfo.DateTimeFormat - * property. For example: - * - * $culture = new CultureInfo('en_AU'); - * $dtfi = $culture->DateTimeFormat; - * - * - * To create a DateTimeFormatInfo for the invariant culture, use - * - * DateTimeFormatInfo::getInstance($culture=null); - * - * you may pass a CultureInfo parameter $culture to get the DateTimeFormatInfo - * for a specific culture. - * - * DateTime values are formatted using standard or custom patterns stored in - * the properties of a DateTimeFormatInfo. - * - * The standard patterns can be replaced with custom patterns by setting the - * associated properties of DateTimeFormatInfo. - * - * The following table lists the standard format characters for each standard - * pattern and the associated DateTimeFormatInfo property that can be set to - * modify the standard pattern. The format characters are case-sensitive; - * for example, 'g' and 'G' represent slightly different patterns. - * - * - * Format Character Associated Property Example Format Pattern (en-US) - * -------------------------------------------------------------------------- - * d ShortDatePattern MM/dd/yyyy - * D LongDatePattern dddd, dd MMMM yyyy - * F FullDateTimePattern dddd, dd MMMM yyyy HH:mm:ss - * m, M MonthDayPattern MMMM dd - * r, R RFC1123Pattern ddd, dd MMM yyyy HH':'mm':'ss 'GMT' - * s SortableDateTimePattern yyyy'-'MM'-'dd'T'HH':'mm':'ss - * t ShortTimePattern HH:mm - * T LongTimePattern HH:mm:ss - * Y YearMonthPattern yyyy MMMM - * -------------------------------------------------------------------------- - * - * - * @author Xiang Wei Zhuo - * @version v1.0, last update on Fri Dec 03 22:30:31 EST 2004 - * @package System.I18N.core - */ -class DateTimeFormatInfo -{ - /** - * ICU date time formatting data. - * @var array - */ - private $data = array(); - - /** - * A list of properties that are accessable/writable. - * @var array - */ - protected $properties = array(); - - /** - * Allow functions that begins with 'set' to be called directly - * as an attribute/property to retrieve the value. - * @return mixed - */ - function __get($name) - { - $getProperty = 'get'.$name; - if(in_array($getProperty, $this->properties)) - return $this->$getProperty(); - else - throw new Exception('Property '.$name.' does not exists.'); - } - - /** - * Allow functions that begins with 'set' to be called directly - * as an attribute/property to set the value. - */ - function __set($name, $value) - { - $setProperty = 'set'.$name; - if(in_array($setProperty, $this->properties)) - $this->$setProperty($value); - else - throw new Exception('Property '.$name.' can not be set.'); - } - - /** - * Initializes a new writable instance of the DateTimeFormatInfo class - * that is dependent on the ICU data for date time formatting - * information. N.B.You should not initialize this class directly - * unless you know what you are doing. Please use use - * DateTimeFormatInfo::getInstance() to create an instance. - * @param array ICU data for date time formatting. - * @see getInstance() - */ - function __construct($data=array()) - { - $this->properties = get_class_methods($this); - - if(empty($data)) - throw new Exception('Please provide the ICU data to initialize.'); - - $this->data = $data; - } - - /** - * Get the internal ICU data for date time formatting. - * @return array ICU date time formatting data. - */ - protected function getData() - { - return $this->data; - } - - /** - * Gets the default DateTimeFormatInfo that is culture-independent - * (invariant). - * @return DateTimeFormatInfo default DateTimeFormatInfo. - */ - static function getInvariantInfo() - { - static $invariant; - if($invariant === null) - { - $culture = CultureInfo::getInvariantCulture(); - $invariant = $culture->getDateTimeFormat(); - } - return $invariant; - } - - /** - * Returns the DateTimeFormatInfo associated with the specified culture. - * @param CultureInfo the culture that gets the DateTimeFormat property. - * @return DateTimeFormatInfo DateTimeFormatInfo for the specified - * culture. - */ - static function getInstance($culture=null) - { - - if ($culture instanceof CultureInfo) - return $culture->getDateTimeFormat(); - else if(is_string($culture)) - { - $cultureInfo = CultureInfo::getInstance($culture); - return $cultureInfo->getDateTimeFormat(); - } - else - { - $cultureInfo = CultureInfo::getInvariantCulture(); - return $cultureInfo->getDateTimeFormat(); - } - } - - /** - * A one-dimensional array of type String containing - * the culture-specific abbreviated names of the days - * of the week. The array for InvariantInfo contains - * "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", and "Sat". - * @return array abbreviated day names - */ - function getAbbreviatedDayNames() - { - return $this->data['dayNames']['format']['abbreviated']; - //return $this->data['dayNames/format/abbreviated']; - } - - /** - * Set the abbreviated day names. The value should be - * an array of string starting with Sunday and ends in Saturady. - * For example, - * array("Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"); - * @param array abbreviated day names. - */ - function setAbbreviatedDayNames($value) - { - $this->data['dayNames']['format']['abbreviated'] = $value; - } - - /** - * A one-dimensional array of type String containing - * the culture-specific narrow names of the days - * of the week. The array for InvariantInfo contains - * "S", "M", "T", "W", "T", "F", and "S". - * @return array narrow day names - */ - function getNarrowDayNames() - { - return $this->data['dayNames']['format']['narrow']; - } - - /** - * Set the narrow day names. The value should be - * an array of string starting with Sunday and ends in Saturady. - * For example, - * array("S", "M", "T", "W", "T", "F", "S"); - * @param array narrow day names. - */ - function setNarrowDayNames($value) - { - $this->data['dayNames']['format']['narrow'] = $value; - } - - /** - * A one-dimensional array of type String containing the - * culture-specific full names of the days of the week. - * The array for InvariantInfo contains "Sunday", "Monday", - * "Tuesday", "Wednesday", "Thursday", "Friday", and "Saturday". - * @return array day names - */ - function getDayNames() - { - return $this->data['dayNames']['format']['wide']; - } - - - /** - * Set the day names. The value should be - * an array of string starting with Sunday and ends in Saturady. - * For example, - * array("Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", - * "Friday", "Saturday".); - * @param array day names. - */ - function setDayNames($value) - { - $this->data['dayNames']['format']['wide'] = $value; - } - - /** - * A one-dimensional array of type String containing the - * culture-specific narrow names of the months. The array - * for InvariantInfo contains "J", "F", "M", "A", "M", "J", - * "J", "A", "S", "O", "N", and "D". - * @return array narrow month names. - */ - function getNarrowMonthNames() - { - return $this->data['monthNames']['format']['narrow']; - } - - /** - * Set the narrow month names. The value should be - * an array of string starting with J and ends in D. - * For example, - * array("J","F","M","A","M","J","J","A","S","O","N","D"); - * @param array month names. - */ - function setNarrowMonthNames($value) - { - $this->data['monthNames']['format']['narrow'] = $value; - } - - /** - * A one-dimensional array of type String containing the - * culture-specific abbreviated names of the months. The array - * for InvariantInfo contains "Jan", "Feb", "Mar", "Apr", "May", - * "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", and "Dec". - * Returns wide names if abbreviated names doesn't exist. - * @return array abbreviated month names. - */ - function getAbbreviatedMonthNames() - { - if (isset($this->data['monthNames']['format']['abbreviated'])) - return $this->data['monthNames']['format']['abbreviated']; - else - return $this->data['monthNames']['format']['wide']; - } - - /** - * Set the abbreviated month names. The value should be - * an array of string starting with Jan and ends in Dec. - * For example, - * array("Jan", "Feb", "Mar", "Apr", "May", "Jun", - * "Jul", "Aug", "Sep","Oct","Nov","Dec"); - * @param array month names. - */ - function setAbbreviatedMonthNames($value) - { - $this->data['monthNames']['format']['abbreviated'] = $value; - } - - /** - * A one-dimensional array of type String containing the - * culture-specific full names of the months. The array for - * InvariantInfo contains "January", "February", "March", "April", - * "May", "June", "July", "August", "September", "October", "November", - * and "December" - * @return array month names. - */ - function getMonthNames() - { - return $this->data['monthNames']['format']['wide']; - } - - /** - * Set the month names. The value should be - * an array of string starting with Janurary and ends in December. - * For example, - * array("January", "February", "March", "April", "May", "June", - * "July", "August", "September","October","November","December"); - * @param array month names. - */ - function setMonthNames($value) - { - $this->data['monthNames']['format']['wide'] = $value; - } - - /** - * A string containing the name of the era. - * @param int era The integer representing the era. - * @return string the era name. - */ - function getEra($era) - { - $eraName = $this->data['eras']['abbreviated']; - return $eraName[$era]; - } - - /** - * The string designator for hours that are "ante meridiem" (before noon). - * The default for InvariantInfo is "AM". - * @return string AM designator. - */ - function getAMDesignator() - { - $result = $this->getAMPMMarkers(); - return $result[0]; - } - - /** - * Set the AM Designator. For example, 'AM'. - * @param string AM designator. - */ - function setAMDesignator($value) - { - $markers = $this->getAMPMMarkers(); - $markers[0] = $value; - $this->setAMPMMarkers($markers); - } - - /** - * The string designator for hours that are "post meridiem" (after noon). - * The default for InvariantInfo is "PM". - * @return string PM designator. - */ - function getPMDesignator() - { - $result = $this->getAMPMMarkers(); - return $result[1]; - } - - /** - * Set the PM Designator. For example, 'PM'. - * @param string PM designator. - */ - function setPMDesignator($value) - { - $markers = $this->getAMPMMarkers(); - $markers[1] = $value; - $this->setAMPMMarkers($markers); - } - - /** - * Get the AM and PM markers array. - * Default InvariantInfo for AM and PM is array('AM','PM'); - * @return array AM and PM markers - */ - function getAMPMMarkers() - { - return $this->data['AmPmMarkers']; - } - - /** - * Set the AM and PM markers array. - * For example array('AM','PM'); - * @param array AM and PM markers - */ - function setAMPMMarkers($value) - { - $this->data['AmPmMarkers'] = $value; - } - - /** - * Returns the full time pattern "HH:mm:ss z" (default). - * This is culture sensitive. - * @return string pattern "HH:mm:ss z". - */ - function getFullTimePattern() - { - return $this->data['DateTimePatterns'][0]; - } - - /** - * Returns the long time pattern "HH:mm:ss z" (default). - * This is culture sensitive. - * @return string pattern "HH:mm:ss z". - */ - function getLongTimePattern() - { - return $this->data['DateTimePatterns'][1]; - } - - /** - * Returns the medium time pattern "HH:mm:ss" (default). - * This is culture sensitive. - * @return string pattern "HH:mm:ss". - */ - function getMediumTimePattern() - { - return $this->data['DateTimePatterns'][2]; - } - - /** - * Returns the short time pattern "HH:mm" (default). - * This is culture sensitive. - * @return string pattern "HH:mm". - */ - function getShortTimePattern() - { - return $this->data['DateTimePatterns'][3]; - } - - /** - * Returns the full date pattern "EEEE, yyyy MMMM dd" (default). - * This is culture sensitive. - * @return string pattern "EEEE, yyyy MMMM dd". - */ - function getFullDatePattern() - { - return $this->data['DateTimePatterns'][4]; - } - - /** - * Returns the long date pattern "yyyy MMMM d" (default). - * This is culture sensitive. - * @return string pattern "yyyy MMMM d". - */ - function getLongDatePattern() - { - return $this->data['DateTimePatterns'][5]; - } - - /** - * Returns the medium date pattern "yyyy MMMM d" (default). - * This is culture sensitive. - * @return string pattern "yyyy MMM d". - */ - function getMediumDatePattern() - { - return $this->data['DateTimePatterns'][6]; - } - - /** - * Returns the short date pattern "yy/MM/dd" (default). - * This is culture sensitive. - * @return string pattern "yy/MM/dd". - */ - function getShortDatePattern() - { - return $this->data['DateTimePatterns'][7]; - } - - /** - * Returns the date time order pattern, "{1} {0}" (default). - * This is culture sensitive. - * @return string pattern "{1} {0}". - */ - function getDateTimeOrderPattern() - { - return $this->data['DateTimePatterns'][8]; - } - - /** - * Formats the date and time in a culture sensitive paterrn. - * The default is "Date Time". - * @return string date and time formated - */ - function formatDateTime($date, $time) - { - $pattern = $this->getDateTimeOrderPattern(); - return str_replace(array('{0}','{1}'), array($time, $date), $pattern); - } - -} + + * @version $Revision: 1.2 $ $Date: 2005/01/05 03:15:14 $ + * @package System.I18N.core + */ + +/** + * Get the CultureInfo class. + */ +require_once(dirname(__FILE__).'/CultureInfo.php'); + + +/** + * Defines how DateTime values are formatted and displayed, depending + * on the culture. + * + * This class contains information, such as date patterns, time patterns, + * and AM/PM designators. + * + * To create a DateTimeFormatInfo for a specific culture, create a + * CultureInfo for that culture and retrieve the CultureInfo.DateTimeFormat + * property. For example: + * + * $culture = new CultureInfo('en_AU'); + * $dtfi = $culture->DateTimeFormat; + * + * + * To create a DateTimeFormatInfo for the invariant culture, use + * + * DateTimeFormatInfo::getInstance($culture=null); + * + * you may pass a CultureInfo parameter $culture to get the DateTimeFormatInfo + * for a specific culture. + * + * DateTime values are formatted using standard or custom patterns stored in + * the properties of a DateTimeFormatInfo. + * + * The standard patterns can be replaced with custom patterns by setting the + * associated properties of DateTimeFormatInfo. + * + * The following table lists the standard format characters for each standard + * pattern and the associated DateTimeFormatInfo property that can be set to + * modify the standard pattern. The format characters are case-sensitive; + * for example, 'g' and 'G' represent slightly different patterns. + * + * + * Format Character Associated Property Example Format Pattern (en-US) + * -------------------------------------------------------------------------- + * d ShortDatePattern MM/dd/yyyy + * D LongDatePattern dddd, dd MMMM yyyy + * F FullDateTimePattern dddd, dd MMMM yyyy HH:mm:ss + * m, M MonthDayPattern MMMM dd + * r, R RFC1123Pattern ddd, dd MMM yyyy HH':'mm':'ss 'GMT' + * s SortableDateTimePattern yyyy'-'MM'-'dd'T'HH':'mm':'ss + * t ShortTimePattern HH:mm + * T LongTimePattern HH:mm:ss + * Y YearMonthPattern yyyy MMMM + * -------------------------------------------------------------------------- + * + * + * @author Xiang Wei Zhuo + * @version v1.0, last update on Fri Dec 03 22:30:31 EST 2004 + * @package System.I18N.core + */ +class DateTimeFormatInfo +{ + /** + * ICU date time formatting data. + * @var array + */ + private $data = array(); + + /** + * A list of properties that are accessable/writable. + * @var array + */ + protected $properties = array(); + + /** + * Allow functions that begins with 'set' to be called directly + * as an attribute/property to retrieve the value. + * @return mixed + */ + function __get($name) + { + $getProperty = 'get'.$name; + if(in_array($getProperty, $this->properties)) + return $this->$getProperty(); + else + throw new Exception('Property '.$name.' does not exists.'); + } + + /** + * Allow functions that begins with 'set' to be called directly + * as an attribute/property to set the value. + */ + function __set($name, $value) + { + $setProperty = 'set'.$name; + if(in_array($setProperty, $this->properties)) + $this->$setProperty($value); + else + throw new Exception('Property '.$name.' can not be set.'); + } + + /** + * Initializes a new writable instance of the DateTimeFormatInfo class + * that is dependent on the ICU data for date time formatting + * information. N.B.You should not initialize this class directly + * unless you know what you are doing. Please use use + * DateTimeFormatInfo::getInstance() to create an instance. + * @param array ICU data for date time formatting. + * @see getInstance() + */ + function __construct($data=array()) + { + $this->properties = get_class_methods($this); + + if(empty($data)) + throw new Exception('Please provide the ICU data to initialize.'); + + $this->data = $data; + } + + /** + * Get the internal ICU data for date time formatting. + * @return array ICU date time formatting data. + */ + protected function getData() + { + return $this->data; + } + + /** + * Gets the default DateTimeFormatInfo that is culture-independent + * (invariant). + * @return DateTimeFormatInfo default DateTimeFormatInfo. + */ + static function getInvariantInfo() + { + static $invariant; + if($invariant === null) + { + $culture = CultureInfo::getInvariantCulture(); + $invariant = $culture->getDateTimeFormat(); + } + return $invariant; + } + + /** + * Returns the DateTimeFormatInfo associated with the specified culture. + * @param CultureInfo the culture that gets the DateTimeFormat property. + * @return DateTimeFormatInfo DateTimeFormatInfo for the specified + * culture. + */ + static function getInstance($culture=null) + { + + if ($culture instanceof CultureInfo) + return $culture->getDateTimeFormat(); + else if(is_string($culture)) + { + $cultureInfo = CultureInfo::getInstance($culture); + return $cultureInfo->getDateTimeFormat(); + } + else + { + $cultureInfo = CultureInfo::getInvariantCulture(); + return $cultureInfo->getDateTimeFormat(); + } + } + + /** + * A one-dimensional array of type String containing + * the culture-specific abbreviated names of the days + * of the week. The array for InvariantInfo contains + * "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", and "Sat". + * @return array abbreviated day names + */ + function getAbbreviatedDayNames() + { + return $this->data['dayNames']['format']['abbreviated']; + //return $this->data['dayNames/format/abbreviated']; + } + + /** + * Set the abbreviated day names. The value should be + * an array of string starting with Sunday and ends in Saturady. + * For example, + * array("Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"); + * @param array abbreviated day names. + */ + function setAbbreviatedDayNames($value) + { + $this->data['dayNames']['format']['abbreviated'] = $value; + } + + /** + * A one-dimensional array of type String containing + * the culture-specific narrow names of the days + * of the week. The array for InvariantInfo contains + * "S", "M", "T", "W", "T", "F", and "S". + * @return array narrow day names + */ + function getNarrowDayNames() + { + return $this->data['dayNames']['format']['narrow']; + } + + /** + * Set the narrow day names. The value should be + * an array of string starting with Sunday and ends in Saturady. + * For example, + * array("S", "M", "T", "W", "T", "F", "S"); + * @param array narrow day names. + */ + function setNarrowDayNames($value) + { + $this->data['dayNames']['format']['narrow'] = $value; + } + + /** + * A one-dimensional array of type String containing the + * culture-specific full names of the days of the week. + * The array for InvariantInfo contains "Sunday", "Monday", + * "Tuesday", "Wednesday", "Thursday", "Friday", and "Saturday". + * @return array day names + */ + function getDayNames() + { + return $this->data['dayNames']['format']['wide']; + } + + + /** + * Set the day names. The value should be + * an array of string starting with Sunday and ends in Saturady. + * For example, + * array("Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", + * "Friday", "Saturday".); + * @param array day names. + */ + function setDayNames($value) + { + $this->data['dayNames']['format']['wide'] = $value; + } + + /** + * A one-dimensional array of type String containing the + * culture-specific narrow names of the months. The array + * for InvariantInfo contains "J", "F", "M", "A", "M", "J", + * "J", "A", "S", "O", "N", and "D". + * @return array narrow month names. + */ + function getNarrowMonthNames() + { + return $this->data['monthNames']['format']['narrow']; + } + + /** + * Set the narrow month names. The value should be + * an array of string starting with J and ends in D. + * For example, + * array("J","F","M","A","M","J","J","A","S","O","N","D"); + * @param array month names. + */ + function setNarrowMonthNames($value) + { + $this->data['monthNames']['format']['narrow'] = $value; + } + + /** + * A one-dimensional array of type String containing the + * culture-specific abbreviated names of the months. The array + * for InvariantInfo contains "Jan", "Feb", "Mar", "Apr", "May", + * "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", and "Dec". + * Returns wide names if abbreviated names doesn't exist. + * @return array abbreviated month names. + */ + function getAbbreviatedMonthNames() + { + if (isset($this->data['monthNames']['format']['abbreviated'])) + return $this->data['monthNames']['format']['abbreviated']; + else + return $this->data['monthNames']['format']['wide']; + } + + /** + * Set the abbreviated month names. The value should be + * an array of string starting with Jan and ends in Dec. + * For example, + * array("Jan", "Feb", "Mar", "Apr", "May", "Jun", + * "Jul", "Aug", "Sep","Oct","Nov","Dec"); + * @param array month names. + */ + function setAbbreviatedMonthNames($value) + { + $this->data['monthNames']['format']['abbreviated'] = $value; + } + + /** + * A one-dimensional array of type String containing the + * culture-specific full names of the months. The array for + * InvariantInfo contains "January", "February", "March", "April", + * "May", "June", "July", "August", "September", "October", "November", + * and "December" + * @return array month names. + */ + function getMonthNames() + { + return $this->data['monthNames']['format']['wide']; + } + + /** + * Set the month names. The value should be + * an array of string starting with Janurary and ends in December. + * For example, + * array("January", "February", "March", "April", "May", "June", + * "July", "August", "September","October","November","December"); + * @param array month names. + */ + function setMonthNames($value) + { + $this->data['monthNames']['format']['wide'] = $value; + } + + /** + * A string containing the name of the era. + * @param int era The integer representing the era. + * @return string the era name. + */ + function getEra($era) + { + $eraName = $this->data['eras']['abbreviated']; + return $eraName[$era]; + } + + /** + * The string designator for hours that are "ante meridiem" (before noon). + * The default for InvariantInfo is "AM". + * @return string AM designator. + */ + function getAMDesignator() + { + $result = $this->getAMPMMarkers(); + return $result[0]; + } + + /** + * Set the AM Designator. For example, 'AM'. + * @param string AM designator. + */ + function setAMDesignator($value) + { + $markers = $this->getAMPMMarkers(); + $markers[0] = $value; + $this->setAMPMMarkers($markers); + } + + /** + * The string designator for hours that are "post meridiem" (after noon). + * The default for InvariantInfo is "PM". + * @return string PM designator. + */ + function getPMDesignator() + { + $result = $this->getAMPMMarkers(); + return $result[1]; + } + + /** + * Set the PM Designator. For example, 'PM'. + * @param string PM designator. + */ + function setPMDesignator($value) + { + $markers = $this->getAMPMMarkers(); + $markers[1] = $value; + $this->setAMPMMarkers($markers); + } + + /** + * Get the AM and PM markers array. + * Default InvariantInfo for AM and PM is array('AM','PM'); + * @return array AM and PM markers + */ + function getAMPMMarkers() + { + return $this->data['AmPmMarkers']; + } + + /** + * Set the AM and PM markers array. + * For example array('AM','PM'); + * @param array AM and PM markers + */ + function setAMPMMarkers($value) + { + $this->data['AmPmMarkers'] = $value; + } + + /** + * Returns the full time pattern "HH:mm:ss z" (default). + * This is culture sensitive. + * @return string pattern "HH:mm:ss z". + */ + function getFullTimePattern() + { + return $this->data['DateTimePatterns'][0]; + } + + /** + * Returns the long time pattern "HH:mm:ss z" (default). + * This is culture sensitive. + * @return string pattern "HH:mm:ss z". + */ + function getLongTimePattern() + { + return $this->data['DateTimePatterns'][1]; + } + + /** + * Returns the medium time pattern "HH:mm:ss" (default). + * This is culture sensitive. + * @return string pattern "HH:mm:ss". + */ + function getMediumTimePattern() + { + return $this->data['DateTimePatterns'][2]; + } + + /** + * Returns the short time pattern "HH:mm" (default). + * This is culture sensitive. + * @return string pattern "HH:mm". + */ + function getShortTimePattern() + { + return $this->data['DateTimePatterns'][3]; + } + + /** + * Returns the full date pattern "EEEE, yyyy MMMM dd" (default). + * This is culture sensitive. + * @return string pattern "EEEE, yyyy MMMM dd". + */ + function getFullDatePattern() + { + return $this->data['DateTimePatterns'][4]; + } + + /** + * Returns the long date pattern "yyyy MMMM d" (default). + * This is culture sensitive. + * @return string pattern "yyyy MMMM d". + */ + function getLongDatePattern() + { + return $this->data['DateTimePatterns'][5]; + } + + /** + * Returns the medium date pattern "yyyy MMMM d" (default). + * This is culture sensitive. + * @return string pattern "yyyy MMM d". + */ + function getMediumDatePattern() + { + return $this->data['DateTimePatterns'][6]; + } + + /** + * Returns the short date pattern "yy/MM/dd" (default). + * This is culture sensitive. + * @return string pattern "yy/MM/dd". + */ + function getShortDatePattern() + { + return $this->data['DateTimePatterns'][7]; + } + + /** + * Returns the date time order pattern, "{1} {0}" (default). + * This is culture sensitive. + * @return string pattern "{1} {0}". + */ + function getDateTimeOrderPattern() + { + return $this->data['DateTimePatterns'][8]; + } + + /** + * Formats the date and time in a culture sensitive paterrn. + * The default is "Date Time". + * @return string date and time formated + */ + function formatDateTime($date, $time) + { + $pattern = $this->getDateTimeOrderPattern(); + return str_replace(array('{0}','{1}'), array($time, $date), $pattern); + } + +} diff --git a/framework/I18N/core/Gettext/MO.php b/framework/I18N/core/Gettext/MO.php index 2a97aee7..4b34034e 100644 --- a/framework/I18N/core/Gettext/MO.php +++ b/framework/I18N/core/Gettext/MO.php @@ -1,355 +1,355 @@ - - * @version $Revision: 1.3 $ $Date: 2005/08/27 03:21:12 $ - * @package System.I18N.core - */ - - -// +----------------------------------------------------------------------+ -// | PEAR :: File :: Gettext :: MO | -// +----------------------------------------------------------------------+ -// | This source file is subject to version 3.0 of the PHP license, | -// | that is available at http://www.php.net/license/3_0.txt | -// | If you did not receive a copy of the PHP license and are unable | -// | to obtain it through the world-wide-web, please send a note to | -// | license@php.net so we can mail you a copy immediately. | -// +----------------------------------------------------------------------+ -// | Copyright (c) 2004 Michael Wallner | -// +----------------------------------------------------------------------+ -// -// $Id$ - -/** - * File::Gettext::MO - * - * @author Michael Wallner - * @license PHP License - */ - -require_once dirname(__FILE__).'/TGettext.php'; - -/** - * File_Gettext_MO - * - * GNU MO file reader and writer. - * - * @author Michael Wallner - * @version $Revision: 1.3 $ - * @access public - * @package System.I18N.core - */ -class TGettext_MO extends TGettext -{ - /** - * file handle - * - * @access private - * @var resource - */ - protected $_handle = null; - - /** - * big endianess - * - * Whether to write with big endian byte order. - * - * @access public - * @var bool - */ - protected $writeBigEndian = false; - - /** - * Constructor - * - * @access public - * @return object File_Gettext_MO - * @param string $file path to GNU MO file - */ - function TGettext_MO($file = '') - { - $this->file = $file; - } - - /** - * _read - * - * @access private - * @return mixed - * @param int $bytes - */ - function _read($bytes = 1) - { - if (0 < $bytes = abs($bytes)) { - return fread($this->_handle, $bytes); - } - return null; - } - - /** - * _readInt - * - * @access private - * @return int - * @param bool $bigendian - */ - function _readInt($bigendian = false) - { - //unpack returns a reference???? - $unpacked = unpack($bigendian ? 'N' : 'V', $this->_read(4)); - return array_shift($unpacked); - } - - /** - * _writeInt - * - * @access private - * @return int - * @param int $int - */ - function _writeInt($int) - { - return $this->_write(pack($this->writeBigEndian ? 'N' : 'V', (int) $int)); - } - - /** - * _write - * - * @access private - * @return int - * @param string $data - */ - function _write($data) - { - return fwrite($this->_handle, $data); - } - - /** - * _writeStr - * - * @access private - * @return int - * @param string $string - */ - function _writeStr($string) - { - return $this->_write($string . "\0"); - } - - /** - * _readStr - * - * @access private - * @return string - * @param array $params associative array with offset and length - * of the string - */ - function _readStr($params) - { - fseek($this->_handle, $params['offset']); - return $this->_read($params['length']); - } - - /** - * Load MO file - * - * @access public - * @return mixed Returns true on success or PEAR_Error on failure. - * @param string $file - */ - function load($file = null) - { - if (!isset($file)) { - $file = $this->file; - } - - // open MO file - if (!is_resource($this->_handle = @fopen($file, 'rb'))) { - return false; - } - // lock MO file shared - if (!@flock($this->_handle, LOCK_SH)) { - @fclose($this->_handle); - return false; - } - - // read (part of) magic number from MO file header and define endianess - - //unpack returns a reference???? - $unpacked = unpack('c', $this->_read(4)); - switch ($magic = array_shift($unpacked)) - { - case -34: - $be = false; - break; - - case -107: - $be = true; - break; - - default: - return false; - } - - // check file format revision - we currently only support 0 - if (0 !== ($_rev = $this->_readInt($be))) { - return false; - } - - // count of strings in this file - $count = $this->_readInt($be); - - // offset of hashing table of the msgids - $offset_original = $this->_readInt($be); - // offset of hashing table of the msgstrs - $offset_translat = $this->_readInt($be); - - // move to msgid hash table - fseek($this->_handle, $offset_original); - // read lengths and offsets of msgids - $original = array(); - for ($i = 0; $i < $count; $i++) { - $original[$i] = array( - 'length' => $this->_readInt($be), - 'offset' => $this->_readInt($be) - ); - } - - // move to msgstr hash table - fseek($this->_handle, $offset_translat); - // read lengths and offsets of msgstrs - $translat = array(); - for ($i = 0; $i < $count; $i++) { - $translat[$i] = array( - 'length' => $this->_readInt($be), - 'offset' => $this->_readInt($be) - ); - } - - // read all - for ($i = 0; $i < $count; $i++) { - $this->strings[$this->_readStr($original[$i])] = - $this->_readStr($translat[$i]); - } - - // done - @flock($this->_handle, LOCK_UN); - @fclose($this->_handle); - $this->_handle = null; - - // check for meta info - if (isset($this->strings[''])) { - $this->meta = parent::meta2array($this->strings['']); - unset($this->strings['']); - } - - return true; - } - - /** - * Save MO file - * - * @access public - * @return mixed Returns true on success or PEAR_Error on failure. - * @param string $file - */ - function save($file = null) - { - if (!isset($file)) { - $file = $this->file; - } - - // open MO file - if (!is_resource($this->_handle = @fopen($file, 'wb'))) { - return false; - } - // lock MO file exclusively - if (!@flock($this->_handle, LOCK_EX)) { - @fclose($this->_handle); - return false; - } - - // write magic number - if ($this->writeBigEndian) { - $this->_write(pack('c*', 0x95, 0x04, 0x12, 0xde)); - } else { - $this->_write(pack('c*', 0xde, 0x12, 0x04, 0x95)); - } - - // write file format revision - $this->_writeInt(0); - - $count = count($this->strings) + ($meta = (count($this->meta) ? 1 : 0)); - // write count of strings - $this->_writeInt($count); - - $offset = 28; - // write offset of orig. strings hash table - $this->_writeInt($offset); - - $offset += ($count * 8); - // write offset transl. strings hash table - $this->_writeInt($offset); - - // write size of hash table (we currently ommit the hash table) - $this->_writeInt(0); - - $offset += ($count * 8); - // write offset of hash table - $this->_writeInt($offset); - - // unshift meta info - if ($this->meta) { - $meta = ''; - foreach ($this->meta as $key => $val) { - $meta .= $key . ': ' . $val . "\n"; - } - $strings = array('' => $meta) + $this->strings; - } else { - $strings = $this->strings; - } - - // write offsets for original strings - foreach (array_keys($strings) as $o) { - $len = strlen($o); - $this->_writeInt($len); - $this->_writeInt($offset); - $offset += $len + 1; - } - - // write offsets for translated strings - foreach ($strings as $t) { - $len = strlen($t); - $this->_writeInt($len); - $this->_writeInt($offset); - $offset += $len + 1; - } - - // write original strings - foreach (array_keys($strings) as $o) { - $this->_writeStr($o); - } - - // write translated strings - foreach ($strings as $t) { - $this->_writeStr($t); - } - - // done - @flock($this->_handle, LOCK_UN); - @fclose($this->_handle); - chmod($file,PRADO_CHMOD); - return true; - } -} + + * @version $Revision: 1.3 $ $Date: 2005/08/27 03:21:12 $ + * @package System.I18N.core + */ + + +// +----------------------------------------------------------------------+ +// | PEAR :: File :: Gettext :: MO | +// +----------------------------------------------------------------------+ +// | This source file is subject to version 3.0 of the PHP license, | +// | that is available at http://www.php.net/license/3_0.txt | +// | If you did not receive a copy of the PHP license and are unable | +// | to obtain it through the world-wide-web, please send a note to | +// | license@php.net so we can mail you a copy immediately. | +// +----------------------------------------------------------------------+ +// | Copyright (c) 2004 Michael Wallner | +// +----------------------------------------------------------------------+ +// +// $Id$ + +/** + * File::Gettext::MO + * + * @author Michael Wallner + * @license PHP License + */ + +require_once dirname(__FILE__).'/TGettext.php'; + +/** + * File_Gettext_MO + * + * GNU MO file reader and writer. + * + * @author Michael Wallner + * @version $Revision: 1.3 $ + * @access public + * @package System.I18N.core + */ +class TGettext_MO extends TGettext +{ + /** + * file handle + * + * @access private + * @var resource + */ + protected $_handle = null; + + /** + * big endianess + * + * Whether to write with big endian byte order. + * + * @access public + * @var bool + */ + protected $writeBigEndian = false; + + /** + * Constructor + * + * @access public + * @return object File_Gettext_MO + * @param string $file path to GNU MO file + */ + function TGettext_MO($file = '') + { + $this->file = $file; + } + + /** + * _read + * + * @access private + * @return mixed + * @param int $bytes + */ + function _read($bytes = 1) + { + if (0 < $bytes = abs($bytes)) { + return fread($this->_handle, $bytes); + } + return null; + } + + /** + * _readInt + * + * @access private + * @return int + * @param bool $bigendian + */ + function _readInt($bigendian = false) + { + //unpack returns a reference???? + $unpacked = unpack($bigendian ? 'N' : 'V', $this->_read(4)); + return array_shift($unpacked); + } + + /** + * _writeInt + * + * @access private + * @return int + * @param int $int + */ + function _writeInt($int) + { + return $this->_write(pack($this->writeBigEndian ? 'N' : 'V', (int) $int)); + } + + /** + * _write + * + * @access private + * @return int + * @param string $data + */ + function _write($data) + { + return fwrite($this->_handle, $data); + } + + /** + * _writeStr + * + * @access private + * @return int + * @param string $string + */ + function _writeStr($string) + { + return $this->_write($string . "\0"); + } + + /** + * _readStr + * + * @access private + * @return string + * @param array $params associative array with offset and length + * of the string + */ + function _readStr($params) + { + fseek($this->_handle, $params['offset']); + return $this->_read($params['length']); + } + + /** + * Load MO file + * + * @access public + * @return mixed Returns true on success or PEAR_Error on failure. + * @param string $file + */ + function load($file = null) + { + if (!isset($file)) { + $file = $this->file; + } + + // open MO file + if (!is_resource($this->_handle = @fopen($file, 'rb'))) { + return false; + } + // lock MO file shared + if (!@flock($this->_handle, LOCK_SH)) { + @fclose($this->_handle); + return false; + } + + // read (part of) magic number from MO file header and define endianess + + //unpack returns a reference???? + $unpacked = unpack('c', $this->_read(4)); + switch ($magic = array_shift($unpacked)) + { + case -34: + $be = false; + break; + + case -107: + $be = true; + break; + + default: + return false; + } + + // check file format revision - we currently only support 0 + if (0 !== ($_rev = $this->_readInt($be))) { + return false; + } + + // count of strings in this file + $count = $this->_readInt($be); + + // offset of hashing table of the msgids + $offset_original = $this->_readInt($be); + // offset of hashing table of the msgstrs + $offset_translat = $this->_readInt($be); + + // move to msgid hash table + fseek($this->_handle, $offset_original); + // read lengths and offsets of msgids + $original = array(); + for ($i = 0; $i < $count; $i++) { + $original[$i] = array( + 'length' => $this->_readInt($be), + 'offset' => $this->_readInt($be) + ); + } + + // move to msgstr hash table + fseek($this->_handle, $offset_translat); + // read lengths and offsets of msgstrs + $translat = array(); + for ($i = 0; $i < $count; $i++) { + $translat[$i] = array( + 'length' => $this->_readInt($be), + 'offset' => $this->_readInt($be) + ); + } + + // read all + for ($i = 0; $i < $count; $i++) { + $this->strings[$this->_readStr($original[$i])] = + $this->_readStr($translat[$i]); + } + + // done + @flock($this->_handle, LOCK_UN); + @fclose($this->_handle); + $this->_handle = null; + + // check for meta info + if (isset($this->strings[''])) { + $this->meta = parent::meta2array($this->strings['']); + unset($this->strings['']); + } + + return true; + } + + /** + * Save MO file + * + * @access public + * @return mixed Returns true on success or PEAR_Error on failure. + * @param string $file + */ + function save($file = null) + { + if (!isset($file)) { + $file = $this->file; + } + + // open MO file + if (!is_resource($this->_handle = @fopen($file, 'wb'))) { + return false; + } + // lock MO file exclusively + if (!@flock($this->_handle, LOCK_EX)) { + @fclose($this->_handle); + return false; + } + + // write magic number + if ($this->writeBigEndian) { + $this->_write(pack('c*', 0x95, 0x04, 0x12, 0xde)); + } else { + $this->_write(pack('c*', 0xde, 0x12, 0x04, 0x95)); + } + + // write file format revision + $this->_writeInt(0); + + $count = count($this->strings) + ($meta = (count($this->meta) ? 1 : 0)); + // write count of strings + $this->_writeInt($count); + + $offset = 28; + // write offset of orig. strings hash table + $this->_writeInt($offset); + + $offset += ($count * 8); + // write offset transl. strings hash table + $this->_writeInt($offset); + + // write size of hash table (we currently ommit the hash table) + $this->_writeInt(0); + + $offset += ($count * 8); + // write offset of hash table + $this->_writeInt($offset); + + // unshift meta info + if ($this->meta) { + $meta = ''; + foreach ($this->meta as $key => $val) { + $meta .= $key . ': ' . $val . "\n"; + } + $strings = array('' => $meta) + $this->strings; + } else { + $strings = $this->strings; + } + + // write offsets for original strings + foreach (array_keys($strings) as $o) { + $len = strlen($o); + $this->_writeInt($len); + $this->_writeInt($offset); + $offset += $len + 1; + } + + // write offsets for translated strings + foreach ($strings as $t) { + $len = strlen($t); + $this->_writeInt($len); + $this->_writeInt($offset); + $offset += $len + 1; + } + + // write original strings + foreach (array_keys($strings) as $o) { + $this->_writeStr($o); + } + + // write translated strings + foreach ($strings as $t) { + $this->_writeStr($t); + } + + // done + @flock($this->_handle, LOCK_UN); + @fclose($this->_handle); + chmod($file,PRADO_CHMOD); + return true; + } +} diff --git a/framework/I18N/core/Gettext/PO.php b/framework/I18N/core/Gettext/PO.php index 54fe10e3..57028f6d 100644 --- a/framework/I18N/core/Gettext/PO.php +++ b/framework/I18N/core/Gettext/PO.php @@ -1,160 +1,160 @@ - - * @version $Revision: 1.2 $ $Date: 2005/01/05 03:15:14 $ - * @package System.I18N.core - */ - -// +----------------------------------------------------------------------+ -// | PEAR :: File :: Gettext :: PO | -// +----------------------------------------------------------------------+ -// | This source file is subject to version 3.0 of the PHP license, | -// | that is available at http://www.php.net/license/3_0.txt | -// | If you did not receive a copy of the PHP license and are unable | -// | to obtain it through the world-wide-web, please send a note to | -// | license@php.net so we can mail you a copy immediately. | -// +----------------------------------------------------------------------+ -// | Copyright (c) 2004 Michael Wallner | -// +----------------------------------------------------------------------+ -// -// $Id$ - -/** - * File::Gettext::PO - * - * @author Michael Wallner - * @license PHP License - */ - -require_once dirname(__FILE__).'/TGettext.php'; - -/** - * File_Gettext_PO - * - * GNU PO file reader and writer. - * - * @author Michael Wallner - * @version $Revision: 1.2 $ - * @access public - * @package System.I18N.core - */ -class TGettext_PO extends TGettext -{ - /** - * Constructor - * - * @access public - * @return object File_Gettext_PO - * @param string path to GNU PO file - */ - function TGettext_PO($file = '') - { - $this->file = $file; - } - - /** - * Load PO file - * - * @access public - * @return mixed Returns true on success or PEAR_Error on failure. - * @param string $file - */ - function load($file = null) - { - if (!isset($file)) { - $file = $this->file; - } - - // load file - if (!$contents = @file($file)) { - return false; - } - $contents = implode('', $contents); - - // match all msgid/msgstr entries - $matched = preg_match_all( - '/(msgid\s+("([^"]|\\\\")*?"\s*)+)\s+' . - '(msgstr\s+("([^"]|\\\\")*?"\s*)+)/', - $contents, $matches - ); - unset($contents); - - if (!$matched) { - return false; - } - - // get all msgids and msgtrs - for ($i = 0; $i < $matched; $i++) { - $msgid = preg_replace( - '/\s*msgid\s*"(.*)"\s*/s', '\\1', $matches[1][$i]); - $msgstr= preg_replace( - '/\s*msgstr\s*"(.*)"\s*/s', '\\1', $matches[4][$i]); - $this->strings[parent::prepare($msgid)] = parent::prepare($msgstr); - } - - // check for meta info - if (isset($this->strings[''])) { - $this->meta = parent::meta2array($this->strings['']); - unset($this->strings['']); - } - - return true; - } - - /** - * Save PO file - * - * @access public - * @return mixed Returns true on success or PEAR_Error on failure. - * @param string $file - */ - function save($file = null) - { - if (!isset($file)) { - $file = $this->file; - } - - // open PO file - if (!is_resource($fh = @fopen($file, 'w'))) { - return false; - } - - // lock PO file exclusively - if (!flock($fh, LOCK_EX)) { - fclose($fh); - return false; - } - // write meta info - if (count($this->meta)) { - $meta = 'msgid ""' . "\nmsgstr " . '""' . "\n"; - foreach ($this->meta as $k => $v) { - $meta .= '"' . $k . ': ' . $v . '\n"' . "\n"; - } - fwrite($fh, $meta . "\n"); - } - // write strings - foreach ($this->strings as $o => $t) { - fwrite($fh, - 'msgid "' . parent::prepare($o, true) . '"' . "\n" . - 'msgstr "' . parent::prepare($t, true) . '"' . "\n\n" - ); - } - - //done - @flock($fh, LOCK_UN); - @fclose($fh); - chmod($file,PRADO_CHMOD); - return true; - } -} + + * @version $Revision: 1.2 $ $Date: 2005/01/05 03:15:14 $ + * @package System.I18N.core + */ + +// +----------------------------------------------------------------------+ +// | PEAR :: File :: Gettext :: PO | +// +----------------------------------------------------------------------+ +// | This source file is subject to version 3.0 of the PHP license, | +// | that is available at http://www.php.net/license/3_0.txt | +// | If you did not receive a copy of the PHP license and are unable | +// | to obtain it through the world-wide-web, please send a note to | +// | license@php.net so we can mail you a copy immediately. | +// +----------------------------------------------------------------------+ +// | Copyright (c) 2004 Michael Wallner | +// +----------------------------------------------------------------------+ +// +// $Id$ + +/** + * File::Gettext::PO + * + * @author Michael Wallner + * @license PHP License + */ + +require_once dirname(__FILE__).'/TGettext.php'; + +/** + * File_Gettext_PO + * + * GNU PO file reader and writer. + * + * @author Michael Wallner + * @version $Revision: 1.2 $ + * @access public + * @package System.I18N.core + */ +class TGettext_PO extends TGettext +{ + /** + * Constructor + * + * @access public + * @return object File_Gettext_PO + * @param string path to GNU PO file + */ + function TGettext_PO($file = '') + { + $this->file = $file; + } + + /** + * Load PO file + * + * @access public + * @return mixed Returns true on success or PEAR_Error on failure. + * @param string $file + */ + function load($file = null) + { + if (!isset($file)) { + $file = $this->file; + } + + // load file + if (!$contents = @file($file)) { + return false; + } + $contents = implode('', $contents); + + // match all msgid/msgstr entries + $matched = preg_match_all( + '/(msgid\s+("([^"]|\\\\")*?"\s*)+)\s+' . + '(msgstr\s+("([^"]|\\\\")*?"\s*)+)/', + $contents, $matches + ); + unset($contents); + + if (!$matched) { + return false; + } + + // get all msgids and msgtrs + for ($i = 0; $i < $matched; $i++) { + $msgid = preg_replace( + '/\s*msgid\s*"(.*)"\s*/s', '\\1', $matches[1][$i]); + $msgstr= preg_replace( + '/\s*msgstr\s*"(.*)"\s*/s', '\\1', $matches[4][$i]); + $this->strings[parent::prepare($msgid)] = parent::prepare($msgstr); + } + + // check for meta info + if (isset($this->strings[''])) { + $this->meta = parent::meta2array($this->strings['']); + unset($this->strings['']); + } + + return true; + } + + /** + * Save PO file + * + * @access public + * @return mixed Returns true on success or PEAR_Error on failure. + * @param string $file + */ + function save($file = null) + { + if (!isset($file)) { + $file = $this->file; + } + + // open PO file + if (!is_resource($fh = @fopen($file, 'w'))) { + return false; + } + + // lock PO file exclusively + if (!flock($fh, LOCK_EX)) { + fclose($fh); + return false; + } + // write meta info + if (count($this->meta)) { + $meta = 'msgid ""' . "\nmsgstr " . '""' . "\n"; + foreach ($this->meta as $k => $v) { + $meta .= '"' . $k . ': ' . $v . '\n"' . "\n"; + } + fwrite($fh, $meta . "\n"); + } + // write strings + foreach ($this->strings as $o => $t) { + fwrite($fh, + 'msgid "' . parent::prepare($o, true) . '"' . "\n" . + 'msgstr "' . parent::prepare($t, true) . '"' . "\n\n" + ); + } + + //done + @flock($fh, LOCK_UN); + @fclose($fh); + chmod($file,PRADO_CHMOD); + return true; + } +} diff --git a/framework/I18N/core/Gettext/TGettext.php b/framework/I18N/core/Gettext/TGettext.php index 39e5d07e..4ca7fadb 100644 --- a/framework/I18N/core/Gettext/TGettext.php +++ b/framework/I18N/core/Gettext/TGettext.php @@ -1,286 +1,286 @@ - - * @version $Revision: 1.4 $ $Date: 2005/01/09 23:36:23 $ - * @package System.I18N.core - */ - -// +----------------------------------------------------------------------+ -// | PEAR :: File :: Gettext | -// +----------------------------------------------------------------------+ -// | This source file is subject to version 3.0 of the PHP license, | -// | that is available at http://www.php.net/license/3_0.txt | -// | If you did not receive a copy of the PHP license and are unable | -// | to obtain it through the world-wide-web, please send a note to | -// | license@php.net so we can mail you a copy immediately. | -// +----------------------------------------------------------------------+ -// | Copyright (c) 2004 Michael Wallner | -// +----------------------------------------------------------------------+ -// -// $Id$ - -/** - * File::Gettext - * - * @author Michael Wallner - * @license PHP License - */ - -/** - * Use PHPs builtin error messages - */ -//ini_set('track_errors', true); - -/** - * File_Gettext - * - * GNU gettext file reader and writer. - * - * ################################################################# - * # All protected members of this class are public in its childs. # - * ################################################################# - * - * @author Michael Wallner - * @version $Revision: 1.4 $ - * @access public - * @package System.I18N.core - */ -class TGettext -{ - /** - * strings - * - * associative array with all [msgid => msgstr] entries - * - * @access protected - * @var array - */ - protected $strings = array(); - - /** - * meta - * - * associative array containing meta - * information like project name or content type - * - * @access protected - * @var array - */ - protected $meta = array(); - - /** - * file path - * - * @access protected - * @var string - */ - protected $file = ''; - - /** - * Factory - * - * @static - * @access public - * @return object Returns File_Gettext_PO or File_Gettext_MO on success - * or PEAR_Error on failure. - * @param string $format MO or PO - * @param string $file path to GNU gettext file - */ - function factory($format, $file = '') - { - $format = strToUpper($format); - $filename = dirname(__FILE__).'/'.$format.'.php'; - if(is_file($filename) == false) - throw new Exception ("Class file $file not found"); - - include_once $filename; - $class = 'TGettext_' . $format; - - return new $class($file); - } - - /** - * poFile2moFile - * - * That's a simple fake of the 'msgfmt' console command. It reads the - * contents of a GNU PO file and saves them to a GNU MO file. - * - * @static - * @access public - * @return mixed Returns true on success or PEAR_Error on failure. - * @param string $pofile path to GNU PO file - * @param string $mofile path to GNU MO file - */ - function poFile2moFile($pofile, $mofile) - { - if (!is_file($pofile)) { - throw new Exception("File $pofile doesn't exist."); - } - - include_once dirname(__FILE__).'/PO.php'; - - $PO = new TGettext_PO($pofile); - if (true !== ($e = $PO->load())) { - return $e; - } - - $MO = $PO->toMO(); - if (true !== ($e = $MO->save($mofile))) { - return $e; - } - unset($PO, $MO); - - return true; - } - - /** - * prepare - * - * @static - * @access protected - * @return string - * @param string $string - * @param bool $reverse - */ - function prepare($string, $reverse = false) - { - if ($reverse) { - $smap = array('"', "\n", "\t", "\r"); - $rmap = array('\"', '\\n"' . "\n" . '"', '\\t', '\\r'); - return (string) str_replace($smap, $rmap, $string); - } else { - $string = preg_replace('/"\s+"/', '', $string); - $smap = array('\\n', '\\r', '\\t', '\"'); - $rmap = array("\n", "\r", "\t", '"'); - return (string) str_replace($smap, $rmap, $string); - } - } - - /** - * meta2array - * - * @static - * @access public - * @return array - * @param string $meta - */ - function meta2array($meta) - { - $array = array(); - foreach (explode("\n", $meta) as $info) { - if ($info = trim($info)) { - list($key, $value) = explode(':', $info, 2); - $array[trim($key)] = trim($value); - } - } - return $array; - } - - /** - * toArray - * - * Returns meta info and strings as an array of a structure like that: - * - * array( - * 'meta' => array( - * 'Content-Type' => 'text/plain; charset=iso-8859-1', - * 'Last-Translator' => 'Michael Wallner ', - * 'PO-Revision-Date' => '2004-07-21 17:03+0200', - * 'Language-Team' => 'German ', - * ), - * 'strings' => array( - * 'All rights reserved' => 'Alle Rechte vorbehalten', - * 'Welcome' => 'Willkommen', - * // ... - * ) - * ) - * - * - * @see fromArray() - * @access protected - * @return array - */ - function toArray() - { - return array('meta' => $this->meta, 'strings' => $this->strings); - } - - /** - * fromArray - * - * Assigns meta info and strings from an array of a structure like that: - * - * array( - * 'meta' => array( - * 'Content-Type' => 'text/plain; charset=iso-8859-1', - * 'Last-Translator' => 'Michael Wallner ', - * 'PO-Revision-Date' => date('Y-m-d H:iO'), - * 'Language-Team' => 'German ', - * ), - * 'strings' => array( - * 'All rights reserved' => 'Alle Rechte vorbehalten', - * 'Welcome' => 'Willkommen', - * // ... - * ) - * ) - * - * - * @see toArray() - * @access protected - * @return bool - * @param array $array - */ - function fromArray($array) - { - if (!array_key_exists('strings', $array)) { - if (count($array) != 2) { - return false; - } else { - list($this->meta, $this->strings) = $array; - } - } else { - $this->meta = @$array['meta']; - $this->strings = @$array['strings']; - } - return true; - } - - /** - * toMO - * - * @access protected - * @return object File_Gettext_MO - */ - function toMO() - { - include_once dirname(__FILE__).'/MO.php'; - $MO = new TGettext_MO; - $MO->fromArray($this->toArray()); - return $MO; - } - - /** - * toPO - * - * @access protected - * @return object File_Gettext_PO - */ - function toPO() - { - include_once dirname(__FILE__).'/PO.php'; - $PO = new TGettext_PO; - $PO->fromArray($this->toArray()); - return $PO; - } -} + + * @version $Revision: 1.4 $ $Date: 2005/01/09 23:36:23 $ + * @package System.I18N.core + */ + +// +----------------------------------------------------------------------+ +// | PEAR :: File :: Gettext | +// +----------------------------------------------------------------------+ +// | This source file is subject to version 3.0 of the PHP license, | +// | that is available at http://www.php.net/license/3_0.txt | +// | If you did not receive a copy of the PHP license and are unable | +// | to obtain it through the world-wide-web, please send a note to | +// | license@php.net so we can mail you a copy immediately. | +// +----------------------------------------------------------------------+ +// | Copyright (c) 2004 Michael Wallner | +// +----------------------------------------------------------------------+ +// +// $Id$ + +/** + * File::Gettext + * + * @author Michael Wallner + * @license PHP License + */ + +/** + * Use PHPs builtin error messages + */ +//ini_set('track_errors', true); + +/** + * File_Gettext + * + * GNU gettext file reader and writer. + * + * ################################################################# + * # All protected members of this class are public in its childs. # + * ################################################################# + * + * @author Michael Wallner + * @version $Revision: 1.4 $ + * @access public + * @package System.I18N.core + */ +class TGettext +{ + /** + * strings + * + * associative array with all [msgid => msgstr] entries + * + * @access protected + * @var array + */ + protected $strings = array(); + + /** + * meta + * + * associative array containing meta + * information like project name or content type + * + * @access protected + * @var array + */ + protected $meta = array(); + + /** + * file path + * + * @access protected + * @var string + */ + protected $file = ''; + + /** + * Factory + * + * @static + * @access public + * @return object Returns File_Gettext_PO or File_Gettext_MO on success + * or PEAR_Error on failure. + * @param string $format MO or PO + * @param string $file path to GNU gettext file + */ + function factory($format, $file = '') + { + $format = strToUpper($format); + $filename = dirname(__FILE__).'/'.$format.'.php'; + if(is_file($filename) == false) + throw new Exception ("Class file $file not found"); + + include_once $filename; + $class = 'TGettext_' . $format; + + return new $class($file); + } + + /** + * poFile2moFile + * + * That's a simple fake of the 'msgfmt' console command. It reads the + * contents of a GNU PO file and saves them to a GNU MO file. + * + * @static + * @access public + * @return mixed Returns true on success or PEAR_Error on failure. + * @param string $pofile path to GNU PO file + * @param string $mofile path to GNU MO file + */ + function poFile2moFile($pofile, $mofile) + { + if (!is_file($pofile)) { + throw new Exception("File $pofile doesn't exist."); + } + + include_once dirname(__FILE__).'/PO.php'; + + $PO = new TGettext_PO($pofile); + if (true !== ($e = $PO->load())) { + return $e; + } + + $MO = $PO->toMO(); + if (true !== ($e = $MO->save($mofile))) { + return $e; + } + unset($PO, $MO); + + return true; + } + + /** + * prepare + * + * @static + * @access protected + * @return string + * @param string $string + * @param bool $reverse + */ + function prepare($string, $reverse = false) + { + if ($reverse) { + $smap = array('"', "\n", "\t", "\r"); + $rmap = array('\"', '\\n"' . "\n" . '"', '\\t', '\\r'); + return (string) str_replace($smap, $rmap, $string); + } else { + $string = preg_replace('/"\s+"/', '', $string); + $smap = array('\\n', '\\r', '\\t', '\"'); + $rmap = array("\n", "\r", "\t", '"'); + return (string) str_replace($smap, $rmap, $string); + } + } + + /** + * meta2array + * + * @static + * @access public + * @return array + * @param string $meta + */ + function meta2array($meta) + { + $array = array(); + foreach (explode("\n", $meta) as $info) { + if ($info = trim($info)) { + list($key, $value) = explode(':', $info, 2); + $array[trim($key)] = trim($value); + } + } + return $array; + } + + /** + * toArray + * + * Returns meta info and strings as an array of a structure like that: + * + * array( + * 'meta' => array( + * 'Content-Type' => 'text/plain; charset=iso-8859-1', + * 'Last-Translator' => 'Michael Wallner ', + * 'PO-Revision-Date' => '2004-07-21 17:03+0200', + * 'Language-Team' => 'German ', + * ), + * 'strings' => array( + * 'All rights reserved' => 'Alle Rechte vorbehalten', + * 'Welcome' => 'Willkommen', + * // ... + * ) + * ) + * + * + * @see fromArray() + * @access protected + * @return array + */ + function toArray() + { + return array('meta' => $this->meta, 'strings' => $this->strings); + } + + /** + * fromArray + * + * Assigns meta info and strings from an array of a structure like that: + * + * array( + * 'meta' => array( + * 'Content-Type' => 'text/plain; charset=iso-8859-1', + * 'Last-Translator' => 'Michael Wallner ', + * 'PO-Revision-Date' => date('Y-m-d H:iO'), + * 'Language-Team' => 'German ', + * ), + * 'strings' => array( + * 'All rights reserved' => 'Alle Rechte vorbehalten', + * 'Welcome' => 'Willkommen', + * // ... + * ) + * ) + * + * + * @see toArray() + * @access protected + * @return bool + * @param array $array + */ + function fromArray($array) + { + if (!array_key_exists('strings', $array)) { + if (count($array) != 2) { + return false; + } else { + list($this->meta, $this->strings) = $array; + } + } else { + $this->meta = @$array['meta']; + $this->strings = @$array['strings']; + } + return true; + } + + /** + * toMO + * + * @access protected + * @return object File_Gettext_MO + */ + function toMO() + { + include_once dirname(__FILE__).'/MO.php'; + $MO = new TGettext_MO; + $MO->fromArray($this->toArray()); + return $MO; + } + + /** + * toPO + * + * @access protected + * @return object File_Gettext_PO + */ + function toPO() + { + include_once dirname(__FILE__).'/PO.php'; + $PO = new TGettext_PO; + $PO->fromArray($this->toArray()); + return $PO; + } +} diff --git a/framework/I18N/core/HTTPNegotiator.php b/framework/I18N/core/HTTPNegotiator.php index 9199ba15..26b532b8 100644 --- a/framework/I18N/core/HTTPNegotiator.php +++ b/framework/I18N/core/HTTPNegotiator.php @@ -1,129 +1,129 @@ - - * @version $Revision: 1.2 $ $Date: 2005/01/05 03:15:14 $ - * @package System.I18N.core - */ - -/** - * Include the CultureInfo class. - */ -require_once(dirname(__FILE__).'/CultureInfo.php'); - -/** - * HTTPNegotiator class. - * - * Get the language and charset information from the client browser. - * - * @author Xiang Wei Zhuo - * @version v1.0, last update on Fri Dec 24 16:01:35 EST 2004 - * @package System.I18N.core - */ -class HTTPNegotiator -{ - /** - * A list of languages accepted by the browser. - * @var array - */ - protected $languages; - - /** - * A list of charsets accepted by the browser - * @var array - */ - protected $charsets; - - /** - * Get a list of languages acceptable by the client browser - * @return array languages ordered in the user browser preferences. - */ - function getLanguages() - { - if($this->languages !== null) { - return $this->languages; - } - - $this->languages = array(); - - if (!isset($_SERVER['HTTP_ACCEPT_LANGUAGE'])) - return $this->languages; - - //$basedir = CultureInfo::dataDir(); - //$ext = CultureInfo::fileExt(); - $info = new CultureInfo(); - - foreach(explode(',', $_SERVER['HTTP_ACCEPT_LANGUAGE']) as $lang) - { - // Cut off any q-value that might come after a semi-colon - if ($pos = strpos($lang, ';')) - $lang = trim(substr($lang, 0, $pos)); - - if (strstr($lang, '-')) - { - $codes = explode('-',$lang); - if($codes[0] == 'i') - { - // Language not listed in ISO 639 that are not variants - // of any listed language, which can be registerd with the - // i-prefix, such as i-cherokee - if(count($codes)>1) - $lang = $codes[1]; - } - else - { - for($i = 0, $k = count($codes); $i<$k; ++$i) - { - if($i == 0) - $lang = strtolower($codes[0]); - else - $lang .= '_'.strtoupper($codes[$i]); - } - } - } - - - - if($info->validCulture($lang)) - $this->languages[] = $lang; - } - - return $this->languages; - } - - /** - * Get a list of charsets acceptable by the client browser. - * @return array list of charsets in preferable order. - */ - function getCharsets() - { - if($this->charsets !== null) { - return $this->charsets; - } - - $this->charsets = array(); - - if (!isset($_SERVER['HTTP_ACCEPT_CHARSET'])) - return $this->charsets; - - foreach (explode(',', $_SERVER['HTTP_ACCEPT_CHARSET']) as $charset) - { - if (!empty($charset)) - $this->charsets[] = preg_replace('/;.*/', '', $charset); - } - - return $this->charsets; - } -} - + + * @version $Revision: 1.2 $ $Date: 2005/01/05 03:15:14 $ + * @package System.I18N.core + */ + +/** + * Include the CultureInfo class. + */ +require_once(dirname(__FILE__).'/CultureInfo.php'); + +/** + * HTTPNegotiator class. + * + * Get the language and charset information from the client browser. + * + * @author Xiang Wei Zhuo + * @version v1.0, last update on Fri Dec 24 16:01:35 EST 2004 + * @package System.I18N.core + */ +class HTTPNegotiator +{ + /** + * A list of languages accepted by the browser. + * @var array + */ + protected $languages; + + /** + * A list of charsets accepted by the browser + * @var array + */ + protected $charsets; + + /** + * Get a list of languages acceptable by the client browser + * @return array languages ordered in the user browser preferences. + */ + function getLanguages() + { + if($this->languages !== null) { + return $this->languages; + } + + $this->languages = array(); + + if (!isset($_SERVER['HTTP_ACCEPT_LANGUAGE'])) + return $this->languages; + + //$basedir = CultureInfo::dataDir(); + //$ext = CultureInfo::fileExt(); + $info = new CultureInfo(); + + foreach(explode(',', $_SERVER['HTTP_ACCEPT_LANGUAGE']) as $lang) + { + // Cut off any q-value that might come after a semi-colon + if ($pos = strpos($lang, ';')) + $lang = trim(substr($lang, 0, $pos)); + + if (strstr($lang, '-')) + { + $codes = explode('-',$lang); + if($codes[0] == 'i') + { + // Language not listed in ISO 639 that are not variants + // of any listed language, which can be registerd with the + // i-prefix, such as i-cherokee + if(count($codes)>1) + $lang = $codes[1]; + } + else + { + for($i = 0, $k = count($codes); $i<$k; ++$i) + { + if($i == 0) + $lang = strtolower($codes[0]); + else + $lang .= '_'.strtoupper($codes[$i]); + } + } + } + + + + if($info->validCulture($lang)) + $this->languages[] = $lang; + } + + return $this->languages; + } + + /** + * Get a list of charsets acceptable by the client browser. + * @return array list of charsets in preferable order. + */ + function getCharsets() + { + if($this->charsets !== null) { + return $this->charsets; + } + + $this->charsets = array(); + + if (!isset($_SERVER['HTTP_ACCEPT_CHARSET'])) + return $this->charsets; + + foreach (explode(',', $_SERVER['HTTP_ACCEPT_CHARSET']) as $charset) + { + if (!empty($charset)) + $this->charsets[] = preg_replace('/;.*/', '', $charset); + } + + return $this->charsets; + } +} + diff --git a/framework/I18N/core/IMessageSource.php b/framework/I18N/core/IMessageSource.php index 1d40bd73..f8263b97 100644 --- a/framework/I18N/core/IMessageSource.php +++ b/framework/I18N/core/IMessageSource.php @@ -1,122 +1,122 @@ - - * @version $Revision: 1.3 $ $Date: 2005/01/09 22:15:32 $ - * @package System.I18N.core - */ - -/** - * IMessageSource interface. - * - * All messages source used by MessageFormat must be of IMessageSource. - * It defines a set of operations to add and retrive messages from the - * message source. In addition, message source can load a particular - * catalogue. - * - * @author Xiang Wei Zhuo - * @version v1.0, last update on Fri Dec 24 17:40:19 EST 2004 - * @package System.I18N.core - */ -interface IMessageSource -{ - /** - * Load the translation table for this particular catalogue. - * The translation should be loaded in the following order. - * # [1] call getCatalogeList($catalogue) to get a list of - * variants for for the specified $catalogue. - * # [2] for each of the variants, call getSource($variant) - * to get the resource, could be a file or catalogue ID. - * # [3] verify that this resource is valid by calling isValidSource($source) - * # [4] try to get the messages from the cache - * # [5] if a cache miss, call load($source) to load the message array - * # [6] store the messages to cache. - * # [7] continue with the foreach loop, e.g. goto [2]. - * - * @param string a catalogue to load - * @return boolean true if loaded, false otherwise. - */ - function load($catalogue = 'messages'); - - /** - * Get the translation table. This includes all the loaded sections. - * It must return a 2 level array of translation strings. - * # "catalogue+variant" the catalogue and its variants. - * # "source string" translation keys, and its translations. - * - * array('catalogue+variant' => - * array('source string' => 'target string', ...) - * ...), - * ...); - * - * - * @return array 2 level array translation table. - */ - function read(); - - /** - * Save the list of untranslated blocks to the translation source. - * If the translation was not found, you should add those - * strings to the translation source via the append() method. - * @param string the catalogue to add to - * @return boolean true if saved successfuly, false otherwise. - */ - function save($catalogue='messages'); - - /** - * Add a untranslated message to the source. Need to call save() - * to save the messages to source. - * @param string message to add - * @return void - */ - function append($message); - - /** - * Delete a particular message from the specified catalogue. - * @param string the source message to delete. - * @param string the catalogue to delete from. - * @return boolean true if deleted, false otherwise. - */ - function delete($message, $catalogue='messages'); - - /** - * Update the translation. - * @param string the source string. - * @param string the new translation string. - * @param string comments - * @param string the catalogue of the translation. - * @return boolean true if translation was updated, false otherwise. - */ - function update($text, $target, $comments, $catalogue='messages'); - - /** - * Returns a list of catalogue as key and all it variants as value. - * @return array list of catalogues - */ - function catalogues(); - - /** - * Set the culture for this particular message source. - * @param string the Culture name. - */ - function setCulture($culture); - - /** - * Get the culture identifier for the source. - * @return string culture identifier. - */ - function getCulture(); - -} - + + * @version $Revision: 1.3 $ $Date: 2005/01/09 22:15:32 $ + * @package System.I18N.core + */ + +/** + * IMessageSource interface. + * + * All messages source used by MessageFormat must be of IMessageSource. + * It defines a set of operations to add and retrive messages from the + * message source. In addition, message source can load a particular + * catalogue. + * + * @author Xiang Wei Zhuo + * @version v1.0, last update on Fri Dec 24 17:40:19 EST 2004 + * @package System.I18N.core + */ +interface IMessageSource +{ + /** + * Load the translation table for this particular catalogue. + * The translation should be loaded in the following order. + * # [1] call getCatalogeList($catalogue) to get a list of + * variants for for the specified $catalogue. + * # [2] for each of the variants, call getSource($variant) + * to get the resource, could be a file or catalogue ID. + * # [3] verify that this resource is valid by calling isValidSource($source) + * # [4] try to get the messages from the cache + * # [5] if a cache miss, call load($source) to load the message array + * # [6] store the messages to cache. + * # [7] continue with the foreach loop, e.g. goto [2]. + * + * @param string a catalogue to load + * @return boolean true if loaded, false otherwise. + */ + function load($catalogue = 'messages'); + + /** + * Get the translation table. This includes all the loaded sections. + * It must return a 2 level array of translation strings. + * # "catalogue+variant" the catalogue and its variants. + * # "source string" translation keys, and its translations. + * + * array('catalogue+variant' => + * array('source string' => 'target string', ...) + * ...), + * ...); + * + * + * @return array 2 level array translation table. + */ + function read(); + + /** + * Save the list of untranslated blocks to the translation source. + * If the translation was not found, you should add those + * strings to the translation source via the append() method. + * @param string the catalogue to add to + * @return boolean true if saved successfuly, false otherwise. + */ + function save($catalogue='messages'); + + /** + * Add a untranslated message to the source. Need to call save() + * to save the messages to source. + * @param string message to add + * @return void + */ + function append($message); + + /** + * Delete a particular message from the specified catalogue. + * @param string the source message to delete. + * @param string the catalogue to delete from. + * @return boolean true if deleted, false otherwise. + */ + function delete($message, $catalogue='messages'); + + /** + * Update the translation. + * @param string the source string. + * @param string the new translation string. + * @param string comments + * @param string the catalogue of the translation. + * @return boolean true if translation was updated, false otherwise. + */ + function update($text, $target, $comments, $catalogue='messages'); + + /** + * Returns a list of catalogue as key and all it variants as value. + * @return array list of catalogues + */ + function catalogues(); + + /** + * Set the culture for this particular message source. + * @param string the Culture name. + */ + function setCulture($culture); + + /** + * Get the culture identifier for the source. + * @return string culture identifier. + */ + function getCulture(); + +} + diff --git a/framework/I18N/core/MessageCache.php b/framework/I18N/core/MessageCache.php index d4f58f1d..44392b79 100644 --- a/framework/I18N/core/MessageCache.php +++ b/framework/I18N/core/MessageCache.php @@ -1,171 +1,171 @@ - $cacheDir, - 'lifeTime' => $this->getLifeTime(), - 'automaticSerialization' => true - ); - - $this->cache = new TCache_Lite($options); - } - - /** - * Get the cache life time. - * @return int Cache life time. - */ - public function getLifeTime() - { - return $this->lifetime; - } - - /** - * Set the cache life time. - * @param int $time Cache life time. - */ - public function setLifeTime($time) - { - $this->lifetime = (int)$time; - } - - /** - * Get the cache file ID based section and locale. - * @param string $catalogue The translation section. - * @param string $culture The translation locale, e.g. "en_AU". - */ - protected function getID($catalogue, $culture) - { - return $catalogue.':'.$culture; - } - - /** - * Get the cache file GROUP based section and locale. - * @param string $catalogue The translation section. - * @param string $culture The translation locale, e.g. "en_AU". - */ - protected function getGroup($catalogue, $culture) - { - return $catalogue.':'.get_class($this); - } - - /** - * Get the data from the cache. - * @param string $catalogue The translation section. - * @param string $culture The translation locale, e.g. "en_AU". - * @param string $filename If the source is a file, this file's modified - * time is newer than the cache's modified time, no cache hit. - * @return mixed Boolean FALSE if no cache hit. Otherwise, translation - * table data for the specified section and locale. - */ - public function get($catalogue, $culture, $lastmodified=0) - { - $ID = $this->getID($catalogue, $culture); - $group = $this->getGroup($catalogue, $culture); - - $this->cache->_setFileName($ID, $group); - - $cache = $this->cache->getCacheFile(); - - if(is_file($cache) == false) - return false; - - - $lastmodified = (int)$lastmodified; - - if($lastmodified <= 0 || $lastmodified > filemtime($cache)) - return false; - - //echo '@@ Cache hit: "'.$ID.'" : "'.$group.'"'; - //echo "
\n"; - - return $this->cache->get($ID, $group); - } - - /** - * Save the data to cache for the specified section and locale. - * @param array $data The data to save. - * @param string $catalogue The translation section. - * @param string $culture The translation locale, e.g. "en_AU". - */ - public function save($data, $catalogue, $culture) - { - $ID = $this->getID($catalogue, $culture); - $group = $this->getGroup($catalogue, $culture); - - //echo '## Cache save: "'.$ID.'" : "'.$group.'"'; - //echo "
\n"; - - return $this->cache->save($data, $ID, $group); - } - - /** - * Clean up the cache for the specified section and locale. - * @param string $catalogue The translation section. - * @param string $culture The translation locale, e.g. "en_AU". - */ - public function clean($catalogue, $culture) - { - $group = $this->getGroup($catalogue, $culture); - $this->cache->clean($group); - } - - /** - * Flush the cache. Deletes all the cache files. - */ - public function clear() - { - $this->cache->clean(); - } - -} - -?> + $cacheDir, + 'lifeTime' => $this->getLifeTime(), + 'automaticSerialization' => true + ); + + $this->cache = new TCache_Lite($options); + } + + /** + * Get the cache life time. + * @return int Cache life time. + */ + public function getLifeTime() + { + return $this->lifetime; + } + + /** + * Set the cache life time. + * @param int $time Cache life time. + */ + public function setLifeTime($time) + { + $this->lifetime = (int)$time; + } + + /** + * Get the cache file ID based section and locale. + * @param string $catalogue The translation section. + * @param string $culture The translation locale, e.g. "en_AU". + */ + protected function getID($catalogue, $culture) + { + return $catalogue.':'.$culture; + } + + /** + * Get the cache file GROUP based section and locale. + * @param string $catalogue The translation section. + * @param string $culture The translation locale, e.g. "en_AU". + */ + protected function getGroup($catalogue, $culture) + { + return $catalogue.':'.get_class($this); + } + + /** + * Get the data from the cache. + * @param string $catalogue The translation section. + * @param string $culture The translation locale, e.g. "en_AU". + * @param string $filename If the source is a file, this file's modified + * time is newer than the cache's modified time, no cache hit. + * @return mixed Boolean FALSE if no cache hit. Otherwise, translation + * table data for the specified section and locale. + */ + public function get($catalogue, $culture, $lastmodified=0) + { + $ID = $this->getID($catalogue, $culture); + $group = $this->getGroup($catalogue, $culture); + + $this->cache->_setFileName($ID, $group); + + $cache = $this->cache->getCacheFile(); + + if(is_file($cache) == false) + return false; + + + $lastmodified = (int)$lastmodified; + + if($lastmodified <= 0 || $lastmodified > filemtime($cache)) + return false; + + //echo '@@ Cache hit: "'.$ID.'" : "'.$group.'"'; + //echo "
\n"; + + return $this->cache->get($ID, $group); + } + + /** + * Save the data to cache for the specified section and locale. + * @param array $data The data to save. + * @param string $catalogue The translation section. + * @param string $culture The translation locale, e.g. "en_AU". + */ + public function save($data, $catalogue, $culture) + { + $ID = $this->getID($catalogue, $culture); + $group = $this->getGroup($catalogue, $culture); + + //echo '## Cache save: "'.$ID.'" : "'.$group.'"'; + //echo "
\n"; + + return $this->cache->save($data, $ID, $group); + } + + /** + * Clean up the cache for the specified section and locale. + * @param string $catalogue The translation section. + * @param string $culture The translation locale, e.g. "en_AU". + */ + public function clean($catalogue, $culture) + { + $group = $this->getGroup($catalogue, $culture); + $this->cache->clean($group); + } + + /** + * Flush the cache. Deletes all the cache files. + */ + public function clear() + { + $this->cache->clean(); + } + +} + +?> diff --git a/framework/I18N/core/MessageFormat.php b/framework/I18N/core/MessageFormat.php index 7af6deb1..fd0d445d 100644 --- a/framework/I18N/core/MessageFormat.php +++ b/framework/I18N/core/MessageFormat.php @@ -1,255 +1,255 @@ - - * @version $Revision: 1.5 $ $Date: 2005/08/27 03:21:12 $ - * @package System.I18N.core - */ - -/** - * Get the MessageSource classes. - */ -require_once(dirname(__FILE__).'/MessageSource.php'); - -/** - * Get the encoding utilities - */ -require_once(dirname(__FILE__).'/util.php'); - -/** - * MessageFormat class. - * - * Format a message, that is, for a particular message find the - * translated message. The following is an example using - * a SQLite database to store the translation message. - * Create a new message format instance and echo "Hello" - * in simplified Chinese. This assumes that the world "Hello" - * is translated in the database. - * - * - * $source = MessageSource::factory('SQLite', 'sqlite://messages.db'); - * $source->setCulture('zh_CN'); - * $source->setCache(new MessageCache('./tmp')); - * - * $formatter = new MessageFormat($source); - * - * echo $formatter->format('Hello'); - * - * - * @author Xiang Wei Zhuo - * @version v1.0, last update on Fri Dec 24 20:46:16 EST 2004 - * @package System.I18N.core - */ -class MessageFormat -{ - /** - * The message source. - * @var MessageSource - */ - protected $source; - - /** - * A list of loaded message catalogues. - * @var array - */ - protected $catagloues = array(); - - /** - * The translation messages. - * @var array - */ - protected $messages = array(); - - /** - * A list of untranslated messages. - * @var array - */ - protected $untranslated = array(); - - /** - * The prefix and suffix to append to untranslated messages. - * @var array - */ - protected $postscript = array('',''); - - /** - * Set the default catalogue. - * @var string - */ - public $Catalogue; - - /** - * Output encoding charset - * @var string - */ - protected $charset = 'UTF-8'; - - /** - * Constructor. - * Create a new instance of MessageFormat using the messages - * from the supplied message source. - * @param MessageSource the source of translation messages. - * @param string charset for the message output. - */ - function __construct(IMessageSource $source, $charset='UTF-8') - { - $this->source = $source; - $this->setCharset($charset); - } - - /** - * Sets the charset for message output. - * @param string charset, default is UTF-8 - */ - public function setCharset($charset) - { - $this->charset = $charset; - } - - /** - * Gets the charset for message output. Default is UTF-8. - * @return string charset, default UTF-8 - */ - public function getCharset() - { - return $this->charset; - } - - /** - * Load the message from a particular catalogue. A listed - * loaded catalogues is kept to prevent reload of the same - * catalogue. The load catalogue messages are stored - * in the $this->message array. - * @param string message catalogue to load. - */ - protected function loadCatalogue($catalogue) - { - if(in_array($catalogue,$this->catagloues)) - return; - - if($this->source->load($catalogue)) - { - $this->messages[$catalogue] = $this->source->read(); - $this->catagloues[] = $catalogue; - } - } - - /** - * Format the string. That is, for a particular string find - * the corresponding translation. Variable subsitution is performed - * for the $args parameter. A different catalogue can be specified - * using the $catalogue parameter. - * The output charset is determined by $this->getCharset(); - * @param string the string to translate. - * @param array a list of string to substitute. - * @param string get the translation from a particular message - * @param string charset, the input AND output charset - * catalogue. - * @return string translated string. - */ - public function format($string,$args=array(), $catalogue=null, $charset=null) - { - if(empty($charset)) $charset = $this->getCharset(); - - //force args as UTF-8 - foreach($args as $k => $v) - $args[$k] = I18N_toUTF8($v, $charset); - $s = $this->formatString(I18N_toUTF8($string, $charset),$args,$catalogue); - return I18N_toEncoding($s, $charset); - } - - /** - * Do string translation. - * @param string the string to translate. - * @param array a list of string to substitute. - * @param string get the translation from a particular message - * catalogue. - * @return string translated string. - */ - protected function formatString($string, $args=array(), $catalogue=null) - { - if(empty($catalogue)) - { - if(empty($this->Catalogue)) - $catalogue = 'messages'; - else - $catalogue = $this->Catalogue; - } - - $this->loadCatalogue($catalogue); - - if(empty($args)) - $args = array(); - - foreach($this->messages[$catalogue] as $variant) - { - // foreach of the translation units - foreach($variant as $source => $result) - { - // we found it, so return the target translation - if($source == $string) - { - //check if it contains only strings. - if(is_string($result)) - $target = $result; - else - { - $target = $result[0]; - } - //found, but untranslated - if(empty($target)) - { - return $this->postscript[0]. - strtr($string, $args). - $this->postscript[1]; - } - else - return strtr($target, $args); - } - } - } - - // well we did not find the translation string. - $this->source->append($string); - - return $this->postscript[0]. - strtr($string, $args). - $this->postscript[1]; - } - - /** - * Get the message source. - * @return MessageSource - */ - function getSource() - { - return $this->source; - } - - /** - * Set the prefix and suffix to append to untranslated messages. - * e.g. $postscript=array('[T]','[/T]'); will output - * "[T]Hello[/T]" if the translation for "Hello" can not be determined. - * @param array first element is the prefix, second element the suffix. - */ - function setUntranslatedPS($postscript) - { - if(is_array($postscript) && count($postscript)>=2) - { - $this->postscript[0] = $postscript[0]; - $this->postscript[1] = $postscript[1]; - } - } -} - + + * @version $Revision: 1.5 $ $Date: 2005/08/27 03:21:12 $ + * @package System.I18N.core + */ + +/** + * Get the MessageSource classes. + */ +require_once(dirname(__FILE__).'/MessageSource.php'); + +/** + * Get the encoding utilities + */ +require_once(dirname(__FILE__).'/util.php'); + +/** + * MessageFormat class. + * + * Format a message, that is, for a particular message find the + * translated message. The following is an example using + * a SQLite database to store the translation message. + * Create a new message format instance and echo "Hello" + * in simplified Chinese. This assumes that the world "Hello" + * is translated in the database. + * + * + * $source = MessageSource::factory('SQLite', 'sqlite://messages.db'); + * $source->setCulture('zh_CN'); + * $source->setCache(new MessageCache('./tmp')); + * + * $formatter = new MessageFormat($source); + * + * echo $formatter->format('Hello'); + * + * + * @author Xiang Wei Zhuo + * @version v1.0, last update on Fri Dec 24 20:46:16 EST 2004 + * @package System.I18N.core + */ +class MessageFormat +{ + /** + * The message source. + * @var MessageSource + */ + protected $source; + + /** + * A list of loaded message catalogues. + * @var array + */ + protected $catagloues = array(); + + /** + * The translation messages. + * @var array + */ + protected $messages = array(); + + /** + * A list of untranslated messages. + * @var array + */ + protected $untranslated = array(); + + /** + * The prefix and suffix to append to untranslated messages. + * @var array + */ + protected $postscript = array('',''); + + /** + * Set the default catalogue. + * @var string + */ + public $Catalogue; + + /** + * Output encoding charset + * @var string + */ + protected $charset = 'UTF-8'; + + /** + * Constructor. + * Create a new instance of MessageFormat using the messages + * from the supplied message source. + * @param MessageSource the source of translation messages. + * @param string charset for the message output. + */ + function __construct(IMessageSource $source, $charset='UTF-8') + { + $this->source = $source; + $this->setCharset($charset); + } + + /** + * Sets the charset for message output. + * @param string charset, default is UTF-8 + */ + public function setCharset($charset) + { + $this->charset = $charset; + } + + /** + * Gets the charset for message output. Default is UTF-8. + * @return string charset, default UTF-8 + */ + public function getCharset() + { + return $this->charset; + } + + /** + * Load the message from a particular catalogue. A listed + * loaded catalogues is kept to prevent reload of the same + * catalogue. The load catalogue messages are stored + * in the $this->message array. + * @param string message catalogue to load. + */ + protected function loadCatalogue($catalogue) + { + if(in_array($catalogue,$this->catagloues)) + return; + + if($this->source->load($catalogue)) + { + $this->messages[$catalogue] = $this->source->read(); + $this->catagloues[] = $catalogue; + } + } + + /** + * Format the string. That is, for a particular string find + * the corresponding translation. Variable subsitution is performed + * for the $args parameter. A different catalogue can be specified + * using the $catalogue parameter. + * The output charset is determined by $this->getCharset(); + * @param string the string to translate. + * @param array a list of string to substitute. + * @param string get the translation from a particular message + * @param string charset, the input AND output charset + * catalogue. + * @return string translated string. + */ + public function format($string,$args=array(), $catalogue=null, $charset=null) + { + if(empty($charset)) $charset = $this->getCharset(); + + //force args as UTF-8 + foreach($args as $k => $v) + $args[$k] = I18N_toUTF8($v, $charset); + $s = $this->formatString(I18N_toUTF8($string, $charset),$args,$catalogue); + return I18N_toEncoding($s, $charset); + } + + /** + * Do string translation. + * @param string the string to translate. + * @param array a list of string to substitute. + * @param string get the translation from a particular message + * catalogue. + * @return string translated string. + */ + protected function formatString($string, $args=array(), $catalogue=null) + { + if(empty($catalogue)) + { + if(empty($this->Catalogue)) + $catalogue = 'messages'; + else + $catalogue = $this->Catalogue; + } + + $this->loadCatalogue($catalogue); + + if(empty($args)) + $args = array(); + + foreach($this->messages[$catalogue] as $variant) + { + // foreach of the translation units + foreach($variant as $source => $result) + { + // we found it, so return the target translation + if($source == $string) + { + //check if it contains only strings. + if(is_string($result)) + $target = $result; + else + { + $target = $result[0]; + } + //found, but untranslated + if(empty($target)) + { + return $this->postscript[0]. + strtr($string, $args). + $this->postscript[1]; + } + else + return strtr($target, $args); + } + } + } + + // well we did not find the translation string. + $this->source->append($string); + + return $this->postscript[0]. + strtr($string, $args). + $this->postscript[1]; + } + + /** + * Get the message source. + * @return MessageSource + */ + function getSource() + { + return $this->source; + } + + /** + * Set the prefix and suffix to append to untranslated messages. + * e.g. $postscript=array('[T]','[/T]'); will output + * "[T]Hello[/T]" if the translation for "Hello" can not be determined. + * @param array first element is the prefix, second element the suffix. + */ + function setUntranslatedPS($postscript) + { + if(is_array($postscript) && count($postscript)>=2) + { + $this->postscript[0] = $postscript[0]; + $this->postscript[1] = $postscript[1]; + } + } +} + diff --git a/framework/I18N/core/MessageSource.php b/framework/I18N/core/MessageSource.php index 76d06e9d..f0f94015 100644 --- a/framework/I18N/core/MessageSource.php +++ b/framework/I18N/core/MessageSource.php @@ -1,336 +1,336 @@ - - * @version $Revision: 1.4 $ $Date: 2005/12/17 06:11:28 $ - * @package System.I18N.core - */ - - /** - * Get the IMessageSource interface. - */ -require_once(dirname(__FILE__).'/IMessageSource.php'); - -/** - * Get the MessageCache class file. - */ -require_once(dirname(__FILE__).'/MessageCache.php'); - -/** - * Abstract MessageSource class. - * - * The base class for all MessageSources. Message sources must be instantiated - * using the factory method. The default valid sources are - * - * # XLIFF -- using XML XLIFF format to store the translation messages. - * # gettext -- Translated messages are stored in the gettext format. - * # Database -- Use an existing TDbConnection to store the messages. - * # SQLite -- (Deprecated) Store the translation messages in a SQLite database. - * # MySQL -- (Deprecated) Using a MySQL database to store the messages. - * - * A custom message source can be instantiated by specifying the filename - * parameter to point to the custom class file. E.g. - * - * $resource = '...'; //custom message source resource - * $classfile = '../MessageSource_MySource.php'; //custom message source - * $source = MessageSource::factory('MySource', $resource, $classfile); - * - * - * If you are writting your own message sources, pay attention to the - * loadCatalogue method. It details how the resources are loaded and cached. - * See also the existing message source types as examples. - * - * The following example instantiates a Database message source, set the culture, - * set the cache handler, and use the source in a message formatter. - * The messages are stored using an existing connection. The source parameter - * for the factory method must contain a valid ConnectionID. - * - * // db1 must be already configured - * $source = MessageSource::factory('Database', 'db1'); - * - * //set the culture and cache, store the cache in the /tmp directory. - * $source->setCulture('en_AU')l - * $source->setCache(new MessageCache('/tmp')); - * - * $formatter = new MessageFormat($source); - * - * - * @author Xiang Wei Zhuo - * @version v1.0, last update on Fri Dec 24 19:55:49 EST 2004 - * @package System.I18N.core - */ -abstract class MessageSource implements IMessageSource -{ - /** - * The culture name for this message source. - * @var string - */ - protected $culture; - - /** - * Array of translation messages. - * @var array - */ - protected $messages = array(); - - /** - * The source of message translations. - * @var string - */ - protected $source; - - /** - * The translation cache. - * @var MessageCache - */ - protected $cache; - - protected $untranslated = array(); - - /** - * Private constructor. MessageSource must be initialized using - * the factory method. - */ - private function __construct() - { - //throw new Exception('Please use the factory method to instantiate.'); - } - - /** - * Factory method to instantiate a new MessageSource depending on the - * source type. The allowed source types are 'XLIFF', 'gettext' and - * 'Database'. The source parameter depends on the source type. - * For 'gettext' and 'XLIFF', 'source' should point to the directory - * where the messages are stored. - * For 'Database', 'source' must be a valid connection id. - * If one of the deprecated types 'MySQL' or 'SQLite' is used, - * 'source' must contain a valid DSN. - * - * Custom message source are possible by supplying the a filename parameter - * in the factory method. - * - * @param string the message source type. - * @param string the location of the resource or the ConnectionID. - * @param string the filename of the custom message source. - * @return MessageSource a new message source of the specified type. - * @throws InvalidMessageSourceTypeException - */ - static function &factory($type, $source='.', $filename='') - { - $types = array('XLIFF','gettext','Database','MySQL','SQLite'); - - if(empty($filename) && !in_array($type, $types)) - throw new Exception('Invalid type "'.$type.'", valid types are '. - implode(', ', $types)); - - $class = 'MessageSource_'.$type; - - if(empty($filename)) - $filename = dirname(__FILE__).'/'.$class.'.php'; - - if(is_file($filename) == false) - throw new Exception("File $filename not found"); - - include_once $filename; - - $obj = new $class($source); - - return $obj; - } - - /** - * Load a particular message catalogue. Use read() to - * to get the array of messages. The catalogue loading sequence - * is as follows - * - * # [1] call getCatalogeList($catalogue) to get a list of - * variants for for the specified $catalogue. - * # [2] for each of the variants, call getSource($variant) - * to get the resource, could be a file or catalogue ID. - * # [3] verify that this resource is valid by calling isValidSource($source) - * # [4] try to get the messages from the cache - * # [5] if a cache miss, call load($source) to load the message array - * # [6] store the messages to cache. - * # [7] continue with the foreach loop, e.g. goto [2]. - * - * @param string a catalogue to load - * @return boolean true if loaded, false otherwise. - * @see read() - */ - function load($catalogue='messages') - { - $variants = $this->getCatalogueList($catalogue); - - $this->messages = array(); - - foreach($variants as $variant) - { - $source = $this->getSource($variant); - - if($this->isValidSource($source) == false) continue; - - $loadData = true; - - if($this->cache) - { - $data = $this->cache->get($variant, - $this->culture, $this->getLastModified($source)); - - if(is_array($data)) - { - $this->messages[$variant] = $data; - $loadData = false; - } - unset($data); - } - if($loadData) - { - $data = &$this->loadData($source); - if(is_array($data)) - { - $this->messages[$variant] = $data; - if($this->cache) - $this->cache->save($data, $variant, $this->culture); - } - unset($data); - } - } - - return true; - } - - /** - * Get the array of messages. - * @param parameter - * @return array translation messages. - */ - public function read() - { - return $this->messages; - } - - /** - * Get the cache handler for this source. - * @return MessageCache cache handler - */ - public function getCache() - { - return $this->cache; - } - - /** - * Set the cache handler for caching the messages. - * @param MessageCache the cache handler. - */ - public function setCache(MessageCache $cache) - { - $this->cache = $cache; - } - - /** - * Add a untranslated message to the source. Need to call save() - * to save the messages to source. - * @param string message to add - */ - public function append($message) - { - if(!in_array($message, $this->untranslated)) - $this->untranslated[] = $message; - } - - /** - * Set the culture for this message source. - * @param string culture name - */ - public function setCulture($culture) - { - $this->culture = $culture; - } - - /** - * Get the culture identifier for the source. - * @return string culture identifier. - */ - public function getCulture() - { - return $this->culture; - } - - /** - * Get the last modified unix-time for this particular catalogue+variant. - * @param string catalogue+variant - * @return int last modified in unix-time format. - */ - protected function getLastModified($source) - { - return 0; - } - - /** - * Load the message for a particular catalogue+variant. - * This methods needs to implemented by subclasses. - * @param string catalogue+variant. - * @return array of translation messages. - */ - protected function &loadData($variant) - { - return array(); - } - - /** - * Get the source, this could be a filename or database ID. - * @param string catalogue+variant - * @return string the resource key - */ - protected function getSource($variant) - { - return $variant; - } - - /** - * Determine if the source is valid. - * @param string catalogue+variant - * @return boolean true if valid, false otherwise. - */ - protected function isValidSource($source) - { - return false; - } - - /** - * Get all the variants of a particular catalogue. - * This method must be implemented by subclasses. - * @param string catalogue name - * @return array list of all variants for this catalogue. - */ - protected function getCatalogueList($catalogue) - { - return array(); - } -} - - -/** - * TMessageSourceIOException thrown when unable to modify message source - * data. - * - * @author Wei Zhuo - * @version $Revision: 1.4 $ $Date: 2005/12/17 06:11:28 ${DATE} ${TIME} $ - * @package System.I18N.core - */ -class TMessageSourceIOException extends TException -{ - -} -?> + + * @version $Revision: 1.4 $ $Date: 2005/12/17 06:11:28 $ + * @package System.I18N.core + */ + + /** + * Get the IMessageSource interface. + */ +require_once(dirname(__FILE__).'/IMessageSource.php'); + +/** + * Get the MessageCache class file. + */ +require_once(dirname(__FILE__).'/MessageCache.php'); + +/** + * Abstract MessageSource class. + * + * The base class for all MessageSources. Message sources must be instantiated + * using the factory method. The default valid sources are + * + * # XLIFF -- using XML XLIFF format to store the translation messages. + * # gettext -- Translated messages are stored in the gettext format. + * # Database -- Use an existing TDbConnection to store the messages. + * # SQLite -- (Deprecated) Store the translation messages in a SQLite database. + * # MySQL -- (Deprecated) Using a MySQL database to store the messages. + * + * A custom message source can be instantiated by specifying the filename + * parameter to point to the custom class file. E.g. + * + * $resource = '...'; //custom message source resource + * $classfile = '../MessageSource_MySource.php'; //custom message source + * $source = MessageSource::factory('MySource', $resource, $classfile); + * + * + * If you are writting your own message sources, pay attention to the + * loadCatalogue method. It details how the resources are loaded and cached. + * See also the existing message source types as examples. + * + * The following example instantiates a Database message source, set the culture, + * set the cache handler, and use the source in a message formatter. + * The messages are stored using an existing connection. The source parameter + * for the factory method must contain a valid ConnectionID. + * + * // db1 must be already configured + * $source = MessageSource::factory('Database', 'db1'); + * + * //set the culture and cache, store the cache in the /tmp directory. + * $source->setCulture('en_AU')l + * $source->setCache(new MessageCache('/tmp')); + * + * $formatter = new MessageFormat($source); + * + * + * @author Xiang Wei Zhuo + * @version v1.0, last update on Fri Dec 24 19:55:49 EST 2004 + * @package System.I18N.core + */ +abstract class MessageSource implements IMessageSource +{ + /** + * The culture name for this message source. + * @var string + */ + protected $culture; + + /** + * Array of translation messages. + * @var array + */ + protected $messages = array(); + + /** + * The source of message translations. + * @var string + */ + protected $source; + + /** + * The translation cache. + * @var MessageCache + */ + protected $cache; + + protected $untranslated = array(); + + /** + * Private constructor. MessageSource must be initialized using + * the factory method. + */ + private function __construct() + { + //throw new Exception('Please use the factory method to instantiate.'); + } + + /** + * Factory method to instantiate a new MessageSource depending on the + * source type. The allowed source types are 'XLIFF', 'gettext' and + * 'Database'. The source parameter depends on the source type. + * For 'gettext' and 'XLIFF', 'source' should point to the directory + * where the messages are stored. + * For 'Database', 'source' must be a valid connection id. + * If one of the deprecated types 'MySQL' or 'SQLite' is used, + * 'source' must contain a valid DSN. + * + * Custom message source are possible by supplying the a filename parameter + * in the factory method. + * + * @param string the message source type. + * @param string the location of the resource or the ConnectionID. + * @param string the filename of the custom message source. + * @return MessageSource a new message source of the specified type. + * @throws InvalidMessageSourceTypeException + */ + static function &factory($type, $source='.', $filename='') + { + $types = array('XLIFF','gettext','Database','MySQL','SQLite'); + + if(empty($filename) && !in_array($type, $types)) + throw new Exception('Invalid type "'.$type.'", valid types are '. + implode(', ', $types)); + + $class = 'MessageSource_'.$type; + + if(empty($filename)) + $filename = dirname(__FILE__).'/'.$class.'.php'; + + if(is_file($filename) == false) + throw new Exception("File $filename not found"); + + include_once $filename; + + $obj = new $class($source); + + return $obj; + } + + /** + * Load a particular message catalogue. Use read() to + * to get the array of messages. The catalogue loading sequence + * is as follows + * + * # [1] call getCatalogeList($catalogue) to get a list of + * variants for for the specified $catalogue. + * # [2] for each of the variants, call getSource($variant) + * to get the resource, could be a file or catalogue ID. + * # [3] verify that this resource is valid by calling isValidSource($source) + * # [4] try to get the messages from the cache + * # [5] if a cache miss, call load($source) to load the message array + * # [6] store the messages to cache. + * # [7] continue with the foreach loop, e.g. goto [2]. + * + * @param string a catalogue to load + * @return boolean true if loaded, false otherwise. + * @see read() + */ + function load($catalogue='messages') + { + $variants = $this->getCatalogueList($catalogue); + + $this->messages = array(); + + foreach($variants as $variant) + { + $source = $this->getSource($variant); + + if($this->isValidSource($source) == false) continue; + + $loadData = true; + + if($this->cache) + { + $data = $this->cache->get($variant, + $this->culture, $this->getLastModified($source)); + + if(is_array($data)) + { + $this->messages[$variant] = $data; + $loadData = false; + } + unset($data); + } + if($loadData) + { + $data = &$this->loadData($source); + if(is_array($data)) + { + $this->messages[$variant] = $data; + if($this->cache) + $this->cache->save($data, $variant, $this->culture); + } + unset($data); + } + } + + return true; + } + + /** + * Get the array of messages. + * @param parameter + * @return array translation messages. + */ + public function read() + { + return $this->messages; + } + + /** + * Get the cache handler for this source. + * @return MessageCache cache handler + */ + public function getCache() + { + return $this->cache; + } + + /** + * Set the cache handler for caching the messages. + * @param MessageCache the cache handler. + */ + public function setCache(MessageCache $cache) + { + $this->cache = $cache; + } + + /** + * Add a untranslated message to the source. Need to call save() + * to save the messages to source. + * @param string message to add + */ + public function append($message) + { + if(!in_array($message, $this->untranslated)) + $this->untranslated[] = $message; + } + + /** + * Set the culture for this message source. + * @param string culture name + */ + public function setCulture($culture) + { + $this->culture = $culture; + } + + /** + * Get the culture identifier for the source. + * @return string culture identifier. + */ + public function getCulture() + { + return $this->culture; + } + + /** + * Get the last modified unix-time for this particular catalogue+variant. + * @param string catalogue+variant + * @return int last modified in unix-time format. + */ + protected function getLastModified($source) + { + return 0; + } + + /** + * Load the message for a particular catalogue+variant. + * This methods needs to implemented by subclasses. + * @param string catalogue+variant. + * @return array of translation messages. + */ + protected function &loadData($variant) + { + return array(); + } + + /** + * Get the source, this could be a filename or database ID. + * @param string catalogue+variant + * @return string the resource key + */ + protected function getSource($variant) + { + return $variant; + } + + /** + * Determine if the source is valid. + * @param string catalogue+variant + * @return boolean true if valid, false otherwise. + */ + protected function isValidSource($source) + { + return false; + } + + /** + * Get all the variants of a particular catalogue. + * This method must be implemented by subclasses. + * @param string catalogue name + * @return array list of all variants for this catalogue. + */ + protected function getCatalogueList($catalogue) + { + return array(); + } +} + + +/** + * TMessageSourceIOException thrown when unable to modify message source + * data. + * + * @author Wei Zhuo + * @version $Revision: 1.4 $ $Date: 2005/12/17 06:11:28 ${DATE} ${TIME} $ + * @package System.I18N.core + */ +class TMessageSourceIOException extends TException +{ + +} +?> diff --git a/framework/I18N/core/MessageSource_Database.php b/framework/I18N/core/MessageSource_Database.php index 3ccea61b..549d1c40 100644 --- a/framework/I18N/core/MessageSource_Database.php +++ b/framework/I18N/core/MessageSource_Database.php @@ -1,323 +1,323 @@ -_connID= (string)$source; - } - - /** - * @return TDbConnection the database connection that may be used to retrieve messages. - */ - public function getDbConnection() - { - if($this->_conn===null) - { - $this->_conn=$this->createDbConnection($this->_connID); - $this->_conn->setActive(true); - } - return $this->_conn; - } - - /** - * Creates the DB connection. - * @param string the module ID for TDataSourceConfig - * @return TDbConnection the created DB connection - * @throws TConfigurationException if module ID is invalid or empty - */ - protected function createDbConnection($connectionID) - { - if($connectionID!=='') - { - $conn=Prado::getApplication()->getModule($connectionID); - if($conn instanceof TDataSourceConfig) - return $conn->getDbConnection(); - else - throw new TConfigurationException('messagesource_connectionid_invalid',$connectionID); - } - else - throw new TConfigurationException('messagesource_connectionid_required'); - } - - /** - * Get an array of messages for a particular catalogue and cultural - * variant. - * @param string the catalogue name + variant - * @return array translation messages. - */ - protected function &loadData($variant) - { - $command=$this->getDBConnection()->createCommand( - 'SELECT t.id, t.source, t.target, t.comments - FROM trans_unit t, catalogue c - WHERE c.cat_id = t.cat_id - AND c.name = :variant - ORDER BY id ASC'); - $command->bindParameter(':variant',$variant,PDO::PARAM_STR); - $dataReader=$command->query(); - - $result = array(); - - foreach ($dataReader as $row) - $result[$row['source']] = array($row['target'],$row['id'],$row['comments']); - - return $result; - } - - /** - * Get the last modified unix-time for this particular catalogue+variant. - * We need to query the database to get the date_modified. - * @param string catalogue+variant - * @return int last modified in unix-time format. - */ - protected function getLastModified($source) - { - $command=$this->getDBConnection()->createCommand( - 'SELECT date_modified FROM catalogue WHERE name = :source'); - $command->bindParameter(':source',$source,PDO::PARAM_STR); - $result=$command->queryScalar(); - return $result ? $result : 0; - } - - - /** - * Check if a particular catalogue+variant exists in the database. - * @param string catalogue+variant - * @return boolean true if the catalogue+variant is in the database, - * false otherwise. - */ - protected function isValidSource($variant) - { - $command=$this->getDBConnection()->createCommand( - 'SELECT COUNT(*) FROM catalogue WHERE name = :variant'); - $command->bindParameter(':variant',$variant,PDO::PARAM_STR); - return $command->queryScalar()==1; - } - - /** - * Get all the variants of a particular catalogue. - * @param string catalogue name - * @return array list of all variants for this catalogue. - */ - protected function getCatalogueList($catalogue) - { - $variants = explode('_',$this->culture); - - $catalogues = array($catalogue); - - $variant = null; - - for($i = 0, $k = count($variants); $i < $k; ++$i) - { - if(isset($variants[$i]{0})) - { - $variant .= ($variant)?'_'.$variants[$i]:$variants[$i]; - $catalogues[] = $catalogue.'.'.$variant; - } - } - return array_reverse($catalogues); - } - - /** - * Retrive catalogue details, array($cat_id, $variant, $count). - * @param string catalogue - * @return array catalogue details, array($cat_id, $variant, $count). - */ - private function getCatalogueDetails($catalogue='messages') - { - if(empty($catalogue)) - $catalogue = 'messages'; - - $variant = $catalogue.'.'.$this->culture; - - $command=$this->getDBConnection()->createCommand( - 'SELECT cat_id FROM catalogue WHERE name = :variant'); - $command->bindParameter(':variant',$variant,PDO::PARAM_STR); - $cat_id=$command->queryScalar(); - - if ($cat_id===null) return false; - - $command=$this->getDBConnection()->createCommand( - 'SELECT COUNT(msg_id) FROM trans_unit WHERE cat_id = :catid '); - $command->bindParameter(':catid',$cat_id,PDO::PARAM_INT); - $count=$command->queryScalar(); - - return array($cat_id, $variant, $count); - } - - /** - * Update the catalogue last modified time. - * @return boolean true if updated, false otherwise. - */ - private function updateCatalogueTime($cat_id, $variant) - { - $time = time(); - $command=$this->getDBConnection()->createCommand( - 'UPDATE catalogue SET date_modified = :moddate WHERE cat_id = :catid'); - $command->bindParameter(':moddate',$time,PDO::PARAM_INT); - $command->bindParameter(':catid',$cat_id,PDO::PARAM_INT); - $result=$command->execute(); - - if(!empty($this->cache)) - $this->cache->clean($variant, $this->culture); - - return $result; - } - - /** - * Save the list of untranslated blocks to the translation source. - * If the translation was not found, you should add those - * strings to the translation source via the append() method. - * @param string the catalogue to add to - * @return boolean true if saved successfuly, false otherwise. - */ - function save($catalogue='messages') - { - $messages = $this->untranslated; - - if(count($messages) <= 0) return false; - - $details = $this->getCatalogueDetails($catalogue); - - if($details) - list($cat_id, $variant, $count) = $details; - else - return false; - - if($cat_id <= 0) return false; - $inserted = 0; - - $time = time(); - - $command=$this->getDBConnection()->createCommand( - 'INSERT INTO trans_unit (cat_id,id,source,date_added) VALUES (:catid,:id,:source,:dateadded)'); - $command->bindParameter(':catid',$cat_id,PDO::PARAM_INT); - $command->bindParameter(':id',$count,PDO::PARAM_INT); - $command->bindParameter(':source',$message,PDO::PARAM_STR); - $command->bindParameter(':dateadded',$time,PDO::PARAM_INT); - foreach($messages as $message) - { - if (empty($message)) continue; - $count++; $inserted++; - $command->execute(); - } - if($inserted > 0) - $this->updateCatalogueTime($cat_id, $variant); - - return $inserted > 0; - } - - /** - * Delete a particular message from the specified catalogue. - * @param string the source message to delete. - * @param string the catalogue to delete from. - * @return boolean true if deleted, false otherwise. - */ - function delete($message, $catalogue='messages') - { - $details = $this->getCatalogueDetails($catalogue); - if($details) - list($cat_id, $variant, $count) = $details; - else - return false; - - $command=$this->getDBConnection()->createCommand( - 'DELETE FROM trans_unit WHERE cat_id = :catid AND source = :message'); - $command->bindParameter(':catid',$cat_id,PDO::PARAM_INT); - $command->bindParameter(':message',$message,PDO::PARAM_STR); - - return ($command->execute()==1) ? $this->updateCatalogueTime($cat_id, $variant) : false; - - } - - /** - * Update the translation. - * @param string the source string. - * @param string the new translation string. - * @param string comments - * @param string the catalogue of the translation. - * @return boolean true if translation was updated, false otherwise. - */ - function update($text, $target, $comments, $catalogue='messages') - { - $details = $this->getCatalogueDetails($catalogue); - if($details) - list($cat_id, $variant, $count) = $details; - else - return false; - - $time = time(); - $command=$this->getDBConnection()->createCommand( - 'UPDATE trans_unit SET target = :target, comments = :comments, date_modified = :datemod - WHERE cat_id = :catid AND source = :source'); - $command->bindParameter(':target',$target,PDO::PARAM_STR); - $command->bindParameter(':comments',$comments,PDO::PARAM_STR); - $command->bindParameter(':datemod',$time,PDO::PARAM_INT); - $command->bindParameter(':catid',$cat_id,PDO::PARAM_INT); - $command->bindParameter(':source',$text,PDO::PARAM_STR); - - return ($command->execute()==1) ? $this->updateCatalogueTime($cat_id, $variant) : false; - } - - /** - * Returns a list of catalogue as key and all it variants as value. - * @return array list of catalogues - */ - function catalogues() - { - $command=$this->getDBConnection()->createCommand( 'SELECT name FROM catalogue ORDER BY name'); - $dataReader=$command->query(); - - $result = array(); - - foreach ($dataReader as $row) - { - $details = explode('.',$row[0]); - if(!isset($details[1])) $details[1] = null; - - $result[] = $details; - } - - return $result; - } - -} -?> +_connID= (string)$source; + } + + /** + * @return TDbConnection the database connection that may be used to retrieve messages. + */ + public function getDbConnection() + { + if($this->_conn===null) + { + $this->_conn=$this->createDbConnection($this->_connID); + $this->_conn->setActive(true); + } + return $this->_conn; + } + + /** + * Creates the DB connection. + * @param string the module ID for TDataSourceConfig + * @return TDbConnection the created DB connection + * @throws TConfigurationException if module ID is invalid or empty + */ + protected function createDbConnection($connectionID) + { + if($connectionID!=='') + { + $conn=Prado::getApplication()->getModule($connectionID); + if($conn instanceof TDataSourceConfig) + return $conn->getDbConnection(); + else + throw new TConfigurationException('messagesource_connectionid_invalid',$connectionID); + } + else + throw new TConfigurationException('messagesource_connectionid_required'); + } + + /** + * Get an array of messages for a particular catalogue and cultural + * variant. + * @param string the catalogue name + variant + * @return array translation messages. + */ + protected function &loadData($variant) + { + $command=$this->getDBConnection()->createCommand( + 'SELECT t.id, t.source, t.target, t.comments + FROM trans_unit t, catalogue c + WHERE c.cat_id = t.cat_id + AND c.name = :variant + ORDER BY id ASC'); + $command->bindParameter(':variant',$variant,PDO::PARAM_STR); + $dataReader=$command->query(); + + $result = array(); + + foreach ($dataReader as $row) + $result[$row['source']] = array($row['target'],$row['id'],$row['comments']); + + return $result; + } + + /** + * Get the last modified unix-time for this particular catalogue+variant. + * We need to query the database to get the date_modified. + * @param string catalogue+variant + * @return int last modified in unix-time format. + */ + protected function getLastModified($source) + { + $command=$this->getDBConnection()->createCommand( + 'SELECT date_modified FROM catalogue WHERE name = :source'); + $command->bindParameter(':source',$source,PDO::PARAM_STR); + $result=$command->queryScalar(); + return $result ? $result : 0; + } + + + /** + * Check if a particular catalogue+variant exists in the database. + * @param string catalogue+variant + * @return boolean true if the catalogue+variant is in the database, + * false otherwise. + */ + protected function isValidSource($variant) + { + $command=$this->getDBConnection()->createCommand( + 'SELECT COUNT(*) FROM catalogue WHERE name = :variant'); + $command->bindParameter(':variant',$variant,PDO::PARAM_STR); + return $command->queryScalar()==1; + } + + /** + * Get all the variants of a particular catalogue. + * @param string catalogue name + * @return array list of all variants for this catalogue. + */ + protected function getCatalogueList($catalogue) + { + $variants = explode('_',$this->culture); + + $catalogues = array($catalogue); + + $variant = null; + + for($i = 0, $k = count($variants); $i < $k; ++$i) + { + if(isset($variants[$i]{0})) + { + $variant .= ($variant)?'_'.$variants[$i]:$variants[$i]; + $catalogues[] = $catalogue.'.'.$variant; + } + } + return array_reverse($catalogues); + } + + /** + * Retrive catalogue details, array($cat_id, $variant, $count). + * @param string catalogue + * @return array catalogue details, array($cat_id, $variant, $count). + */ + private function getCatalogueDetails($catalogue='messages') + { + if(empty($catalogue)) + $catalogue = 'messages'; + + $variant = $catalogue.'.'.$this->culture; + + $command=$this->getDBConnection()->createCommand( + 'SELECT cat_id FROM catalogue WHERE name = :variant'); + $command->bindParameter(':variant',$variant,PDO::PARAM_STR); + $cat_id=$command->queryScalar(); + + if ($cat_id===null) return false; + + $command=$this->getDBConnection()->createCommand( + 'SELECT COUNT(msg_id) FROM trans_unit WHERE cat_id = :catid '); + $command->bindParameter(':catid',$cat_id,PDO::PARAM_INT); + $count=$command->queryScalar(); + + return array($cat_id, $variant, $count); + } + + /** + * Update the catalogue last modified time. + * @return boolean true if updated, false otherwise. + */ + private function updateCatalogueTime($cat_id, $variant) + { + $time = time(); + $command=$this->getDBConnection()->createCommand( + 'UPDATE catalogue SET date_modified = :moddate WHERE cat_id = :catid'); + $command->bindParameter(':moddate',$time,PDO::PARAM_INT); + $command->bindParameter(':catid',$cat_id,PDO::PARAM_INT); + $result=$command->execute(); + + if(!empty($this->cache)) + $this->cache->clean($variant, $this->culture); + + return $result; + } + + /** + * Save the list of untranslated blocks to the translation source. + * If the translation was not found, you should add those + * strings to the translation source via the append() method. + * @param string the catalogue to add to + * @return boolean true if saved successfuly, false otherwise. + */ + function save($catalogue='messages') + { + $messages = $this->untranslated; + + if(count($messages) <= 0) return false; + + $details = $this->getCatalogueDetails($catalogue); + + if($details) + list($cat_id, $variant, $count) = $details; + else + return false; + + if($cat_id <= 0) return false; + $inserted = 0; + + $time = time(); + + $command=$this->getDBConnection()->createCommand( + 'INSERT INTO trans_unit (cat_id,id,source,date_added) VALUES (:catid,:id,:source,:dateadded)'); + $command->bindParameter(':catid',$cat_id,PDO::PARAM_INT); + $command->bindParameter(':id',$count,PDO::PARAM_INT); + $command->bindParameter(':source',$message,PDO::PARAM_STR); + $command->bindParameter(':dateadded',$time,PDO::PARAM_INT); + foreach($messages as $message) + { + if (empty($message)) continue; + $count++; $inserted++; + $command->execute(); + } + if($inserted > 0) + $this->updateCatalogueTime($cat_id, $variant); + + return $inserted > 0; + } + + /** + * Delete a particular message from the specified catalogue. + * @param string the source message to delete. + * @param string the catalogue to delete from. + * @return boolean true if deleted, false otherwise. + */ + function delete($message, $catalogue='messages') + { + $details = $this->getCatalogueDetails($catalogue); + if($details) + list($cat_id, $variant, $count) = $details; + else + return false; + + $command=$this->getDBConnection()->createCommand( + 'DELETE FROM trans_unit WHERE cat_id = :catid AND source = :message'); + $command->bindParameter(':catid',$cat_id,PDO::PARAM_INT); + $command->bindParameter(':message',$message,PDO::PARAM_STR); + + return ($command->execute()==1) ? $this->updateCatalogueTime($cat_id, $variant) : false; + + } + + /** + * Update the translation. + * @param string the source string. + * @param string the new translation string. + * @param string comments + * @param string the catalogue of the translation. + * @return boolean true if translation was updated, false otherwise. + */ + function update($text, $target, $comments, $catalogue='messages') + { + $details = $this->getCatalogueDetails($catalogue); + if($details) + list($cat_id, $variant, $count) = $details; + else + return false; + + $time = time(); + $command=$this->getDBConnection()->createCommand( + 'UPDATE trans_unit SET target = :target, comments = :comments, date_modified = :datemod + WHERE cat_id = :catid AND source = :source'); + $command->bindParameter(':target',$target,PDO::PARAM_STR); + $command->bindParameter(':comments',$comments,PDO::PARAM_STR); + $command->bindParameter(':datemod',$time,PDO::PARAM_INT); + $command->bindParameter(':catid',$cat_id,PDO::PARAM_INT); + $command->bindParameter(':source',$text,PDO::PARAM_STR); + + return ($command->execute()==1) ? $this->updateCatalogueTime($cat_id, $variant) : false; + } + + /** + * Returns a list of catalogue as key and all it variants as value. + * @return array list of catalogues + */ + function catalogues() + { + $command=$this->getDBConnection()->createCommand( 'SELECT name FROM catalogue ORDER BY name'); + $dataReader=$command->query(); + + $result = array(); + + foreach ($dataReader as $row) + { + $details = explode('.',$row[0]); + if(!isset($details[1])) $details[1] = null; + + $result[] = $details; + } + + return $result; + } + +} +?> diff --git a/framework/I18N/core/MessageSource_MySQL.php b/framework/I18N/core/MessageSource_MySQL.php index 080b89bc..0cd893d4 100644 --- a/framework/I18N/core/MessageSource_MySQL.php +++ b/framework/I18N/core/MessageSource_MySQL.php @@ -1,418 +1,418 @@ - - * @version $Revision: 1.4 $ $Date: 2005/02/25 09:59:40 $ - * @package System.I18N.core - */ - -/** - * Get the MessageSource class file. - */ -require_once(dirname(__FILE__).'/MessageSource.php'); - -/** - * Get the I18N utility file, contains the DSN parser. - */ -require_once(dirname(__FILE__).'/util.php'); - -/** - * MessageSource_MySQL class. - * - * Retrive the message translation from a MySQL database. - * - * See the MessageSource::factory() method to instantiate this class. - * - * @author Xiang Wei Zhuo - * @version v1.0, last update on Fri Dec 24 16:58:58 EST 2004 - * @package System.I18N.core - */ -class MessageSource_MySQL extends MessageSource -{ - /** - * The datasource string, full DSN to the database. - * @var string - */ - protected $source; - - /** - * The DSN array property, parsed by PEAR's DB DSN parser. - * @var array - */ - protected $dns; - - /** - * A resource link to the database - * @var db - */ - protected $db; - /** - * Constructor. - * Create a new message source using MySQL. - * @param string MySQL datasource, in PEAR's DB DSN format. - * @see MessageSource::factory(); - */ - function __construct($source) - { - $this->source = (string)$source; - $this->dns = parseDSN($this->source); - $this->db = $this->connect(); - } - - /** - * Destructor, close the database connection. - */ - function __destruct() - { - @mysql_close($this->db); - } - - /** - * Connect to the MySQL datasource - * @return resource MySQL connection. - * @throws Exception, connection and database errors. - */ - protected function connect() - { - /*static $conn; - - if($conn!==null) - return $conn; - */ - $dsninfo = $this->dns; - - if (isset($dsninfo['protocol']) && $dsninfo['protocol'] == 'unix') - $dbhost = ':' . $dsninfo['socket']; - else - { - $dbhost = $dsninfo['hostspec'] ? $dsninfo['hostspec'] : 'localhost'; - if (!empty($dsninfo['port'])) - $dbhost .= ':' . $dsninfo['port']; - } - $user = $dsninfo['username']; - $pw = $dsninfo['password']; - - $connect_function = 'mysql_connect'; - - if ($dbhost && $user && $pw) - $conn = @$connect_function($dbhost, $user, $pw); - elseif ($dbhost && $user) - $conn = @$connect_function($dbhost, $user); - elseif ($dbhost) - $conn = @$connect_function($dbhost); - else - $conn = false; - - if (empty($conn)) - { - throw new Exception('Error in connecting to '.$dsninfo); - } - - if ($dsninfo['database']) - { - if (!@mysql_select_db($dsninfo['database'], $conn)) - throw new Exception('Error in connecting database, dns:'. - $dsninfo); - } - else - throw new Exception('Please provide a database for message'. - ' translation.'); - return $conn; - } - - /** - * Get the database connection. - * @return db database connection. - */ - public function connection() - { - return $this->db; - } - - /** - * Get an array of messages for a particular catalogue and cultural - * variant. - * @param string the catalogue name + variant - * @return array translation messages. - */ - protected function &loadData($variant) - { - $variant = mysql_real_escape_string($variant); - - $statement = - "SELECT t.id, t.source, t.target, t.comments - FROM trans_unit t, catalogue c - WHERE c.cat_id = t.cat_id - AND c.name = '{$variant}' - ORDER BY id ASC"; - - $rs = mysql_query($statement,$this->db); - - $result = array(); - - while($row = mysql_fetch_array($rs,MYSQL_NUM)) - { - $source = $row[1]; - $result[$source][] = $row[2]; //target - $result[$source][] = $row[0]; //id - $result[$source][] = $row[3]; //comments - } - - return $result; - } - - /** - * Get the last modified unix-time for this particular catalogue+variant. - * We need to query the database to get the date_modified. - * @param string catalogue+variant - * @return int last modified in unix-time format. - */ - protected function getLastModified($source) - { - $source = mysql_real_escape_string($source); - - $rs = mysql_query( - "SELECT date_modified FROM catalogue WHERE name = '{$source}'", - $this->db); - - $result = $rs ? (int)mysql_result($rs,0) : 0; - - return $result; - } - - /** - * Check if a particular catalogue+variant exists in the database. - * @param string catalogue+variant - * @return boolean true if the catalogue+variant is in the database, - * false otherwise. - */ - protected function isValidSource($variant) - { - $variant = mysql_real_escape_string ($variant); - - $rs = mysql_query( - "SELECT COUNT(*) FROM catalogue WHERE name = '{$variant}'", - $this->db); - - $row = mysql_fetch_array($rs,MYSQL_NUM); - - $result = $row && $row[0] == '1'; - - return $result; - } - - /** - * Get all the variants of a particular catalogue. - * @param string catalogue name - * @return array list of all variants for this catalogue. - */ - protected function getCatalogueList($catalogue) - { - $variants = explode('_',$this->culture); - - $catalogues = array($catalogue); - - $variant = null; - - for($i = 0, $k = count($variants); $i < $k; ++$i) - { - if(isset($variants[$i]{0})) - { - $variant .= ($variant)?'_'.$variants[$i]:$variants[$i]; - $catalogues[] = $catalogue.'.'.$variant; - } - } - return array_reverse($catalogues); - } - - /** - * Retrive catalogue details, array($cat_id, $variant, $count). - * @param string catalogue - * @return array catalogue details, array($cat_id, $variant, $count). - */ - private function getCatalogueDetails($catalogue='messages') - { - if(empty($catalogue)) - $catalogue = 'messages'; - - $variant = $catalogue.'.'.$this->culture; - - $name = mysql_real_escape_string($this->getSource($variant)); - - $rs = mysql_query("SELECT cat_id - FROM catalogue WHERE name = '{$name}'", $this->db); - - if(mysql_num_rows($rs) != 1) - return false; - - $cat_id = (int)mysql_result($rs,0); - - //first get the catalogue ID - $rs = mysql_query( - "SELECT count(msg_id) - FROM trans_unit - WHERE cat_id = {$cat_id}", $this->db); - - $count = (int)mysql_result($rs,0); - - return array($cat_id, $variant, $count); - } - - /** - * Update the catalogue last modified time. - * @return boolean true if updated, false otherwise. - */ - private function updateCatalogueTime($cat_id, $variant) - { - $time = time(); - - $result = mysql_query("UPDATE catalogue - SET date_modified = {$time} - WHERE cat_id = {$cat_id}", $this->db); - - if(!empty($this->cache)) - $this->cache->clean($variant, $this->culture); - - return $result; - } - - /** - * Save the list of untranslated blocks to the translation source. - * If the translation was not found, you should add those - * strings to the translation source via the append() method. - * @param string the catalogue to add to - * @return boolean true if saved successfuly, false otherwise. - */ - function save($catalogue='messages') - { - $messages = $this->untranslated; - - if(count($messages) <= 0) return false; - - $details = $this->getCatalogueDetails($catalogue); - - if($details) - list($cat_id, $variant, $count) = $details; - else - return false; - - if($cat_id <= 0) return false; - $inserted = 0; - - $time = time(); - - foreach($messages as $message) - { - $count++; $inserted++; - $message = mysql_real_escape_string($message); - $statement = "INSERT INTO trans_unit - (cat_id,id,source,date_added) VALUES - ({$cat_id}, {$count},'{$message}',$time)"; - mysql_query($statement, $this->db); - } - if($inserted > 0) - $this->updateCatalogueTime($cat_id, $variant); - - return $inserted > 0; - } - - /** - * Delete a particular message from the specified catalogue. - * @param string the source message to delete. - * @param string the catalogue to delete from. - * @return boolean true if deleted, false otherwise. - */ - function delete($message, $catalogue='messages') - { - $details = $this->getCatalogueDetails($catalogue); - if($details) - list($cat_id, $variant, $count) = $details; - else - return false; - - $text = mysql_real_escape_string($message); - - $statement = "DELETE FROM trans_unit WHERE - cat_id = {$cat_id} AND source = '{$message}'"; - $deleted = false; - - mysql_query($statement, $this->db); - - if(mysql_affected_rows($this->db) == 1) - $deleted = $this->updateCatalogueTime($cat_id, $variant); - - return $deleted; - - } - - /** - * Update the translation. - * @param string the source string. - * @param string the new translation string. - * @param string comments - * @param string the catalogue of the translation. - * @return boolean true if translation was updated, false otherwise. - */ - function update($text, $target, $comments, $catalogue='messages') - { - $details = $this->getCatalogueDetails($catalogue); - if($details) - list($cat_id, $variant, $count) = $details; - else - return false; - - $comments = mysql_real_escape_string($comments); - $target = mysql_real_escape_string($target); - $text = mysql_real_escape_string($text); - - $time = time(); - - $statement = "UPDATE trans_unit SET - target = '{$target}', - comments = '{$comments}', - date_modified = '{$time}' - WHERE cat_id = {$cat_id} - AND source = '{$text}'"; - - $updated = false; - - mysql_query($statement, $this->db); - if(mysql_affected_rows($this->db) == 1) - $updated = $this->updateCatalogueTime($cat_id, $variant); - - return $updated; - } - - /** - * Returns a list of catalogue as key and all it variants as value. - * @return array list of catalogues - */ - function catalogues() - { - $statement = 'SELECT name FROM catalogue ORDER BY name'; - $rs = mysql_query($statement, $this->db); - $result = array(); - while($row = mysql_fetch_array($rs,MYSQL_NUM)) - { - $details = explode('.',$row[0]); - if(!isset($details[1])) $details[1] = null; - - $result[] = $details; - } - return $result; - } - -} - -?> + + * @version $Revision: 1.4 $ $Date: 2005/02/25 09:59:40 $ + * @package System.I18N.core + */ + +/** + * Get the MessageSource class file. + */ +require_once(dirname(__FILE__).'/MessageSource.php'); + +/** + * Get the I18N utility file, contains the DSN parser. + */ +require_once(dirname(__FILE__).'/util.php'); + +/** + * MessageSource_MySQL class. + * + * Retrive the message translation from a MySQL database. + * + * See the MessageSource::factory() method to instantiate this class. + * + * @author Xiang Wei Zhuo + * @version v1.0, last update on Fri Dec 24 16:58:58 EST 2004 + * @package System.I18N.core + */ +class MessageSource_MySQL extends MessageSource +{ + /** + * The datasource string, full DSN to the database. + * @var string + */ + protected $source; + + /** + * The DSN array property, parsed by PEAR's DB DSN parser. + * @var array + */ + protected $dns; + + /** + * A resource link to the database + * @var db + */ + protected $db; + /** + * Constructor. + * Create a new message source using MySQL. + * @param string MySQL datasource, in PEAR's DB DSN format. + * @see MessageSource::factory(); + */ + function __construct($source) + { + $this->source = (string)$source; + $this->dns = parseDSN($this->source); + $this->db = $this->connect(); + } + + /** + * Destructor, close the database connection. + */ + function __destruct() + { + @mysql_close($this->db); + } + + /** + * Connect to the MySQL datasource + * @return resource MySQL connection. + * @throws Exception, connection and database errors. + */ + protected function connect() + { + /*static $conn; + + if($conn!==null) + return $conn; + */ + $dsninfo = $this->dns; + + if (isset($dsninfo['protocol']) && $dsninfo['protocol'] == 'unix') + $dbhost = ':' . $dsninfo['socket']; + else + { + $dbhost = $dsninfo['hostspec'] ? $dsninfo['hostspec'] : 'localhost'; + if (!empty($dsninfo['port'])) + $dbhost .= ':' . $dsninfo['port']; + } + $user = $dsninfo['username']; + $pw = $dsninfo['password']; + + $connect_function = 'mysql_connect'; + + if ($dbhost && $user && $pw) + $conn = @$connect_function($dbhost, $user, $pw); + elseif ($dbhost && $user) + $conn = @$connect_function($dbhost, $user); + elseif ($dbhost) + $conn = @$connect_function($dbhost); + else + $conn = false; + + if (empty($conn)) + { + throw new Exception('Error in connecting to '.$dsninfo); + } + + if ($dsninfo['database']) + { + if (!@mysql_select_db($dsninfo['database'], $conn)) + throw new Exception('Error in connecting database, dns:'. + $dsninfo); + } + else + throw new Exception('Please provide a database for message'. + ' translation.'); + return $conn; + } + + /** + * Get the database connection. + * @return db database connection. + */ + public function connection() + { + return $this->db; + } + + /** + * Get an array of messages for a particular catalogue and cultural + * variant. + * @param string the catalogue name + variant + * @return array translation messages. + */ + protected function &loadData($variant) + { + $variant = mysql_real_escape_string($variant); + + $statement = + "SELECT t.id, t.source, t.target, t.comments + FROM trans_unit t, catalogue c + WHERE c.cat_id = t.cat_id + AND c.name = '{$variant}' + ORDER BY id ASC"; + + $rs = mysql_query($statement,$this->db); + + $result = array(); + + while($row = mysql_fetch_array($rs,MYSQL_NUM)) + { + $source = $row[1]; + $result[$source][] = $row[2]; //target + $result[$source][] = $row[0]; //id + $result[$source][] = $row[3]; //comments + } + + return $result; + } + + /** + * Get the last modified unix-time for this particular catalogue+variant. + * We need to query the database to get the date_modified. + * @param string catalogue+variant + * @return int last modified in unix-time format. + */ + protected function getLastModified($source) + { + $source = mysql_real_escape_string($source); + + $rs = mysql_query( + "SELECT date_modified FROM catalogue WHERE name = '{$source}'", + $this->db); + + $result = $rs ? (int)mysql_result($rs,0) : 0; + + return $result; + } + + /** + * Check if a particular catalogue+variant exists in the database. + * @param string catalogue+variant + * @return boolean true if the catalogue+variant is in the database, + * false otherwise. + */ + protected function isValidSource($variant) + { + $variant = mysql_real_escape_string ($variant); + + $rs = mysql_query( + "SELECT COUNT(*) FROM catalogue WHERE name = '{$variant}'", + $this->db); + + $row = mysql_fetch_array($rs,MYSQL_NUM); + + $result = $row && $row[0] == '1'; + + return $result; + } + + /** + * Get all the variants of a particular catalogue. + * @param string catalogue name + * @return array list of all variants for this catalogue. + */ + protected function getCatalogueList($catalogue) + { + $variants = explode('_',$this->culture); + + $catalogues = array($catalogue); + + $variant = null; + + for($i = 0, $k = count($variants); $i < $k; ++$i) + { + if(isset($variants[$i]{0})) + { + $variant .= ($variant)?'_'.$variants[$i]:$variants[$i]; + $catalogues[] = $catalogue.'.'.$variant; + } + } + return array_reverse($catalogues); + } + + /** + * Retrive catalogue details, array($cat_id, $variant, $count). + * @param string catalogue + * @return array catalogue details, array($cat_id, $variant, $count). + */ + private function getCatalogueDetails($catalogue='messages') + { + if(empty($catalogue)) + $catalogue = 'messages'; + + $variant = $catalogue.'.'.$this->culture; + + $name = mysql_real_escape_string($this->getSource($variant)); + + $rs = mysql_query("SELECT cat_id + FROM catalogue WHERE name = '{$name}'", $this->db); + + if(mysql_num_rows($rs) != 1) + return false; + + $cat_id = (int)mysql_result($rs,0); + + //first get the catalogue ID + $rs = mysql_query( + "SELECT count(msg_id) + FROM trans_unit + WHERE cat_id = {$cat_id}", $this->db); + + $count = (int)mysql_result($rs,0); + + return array($cat_id, $variant, $count); + } + + /** + * Update the catalogue last modified time. + * @return boolean true if updated, false otherwise. + */ + private function updateCatalogueTime($cat_id, $variant) + { + $time = time(); + + $result = mysql_query("UPDATE catalogue + SET date_modified = {$time} + WHERE cat_id = {$cat_id}", $this->db); + + if(!empty($this->cache)) + $this->cache->clean($variant, $this->culture); + + return $result; + } + + /** + * Save the list of untranslated blocks to the translation source. + * If the translation was not found, you should add those + * strings to the translation source via the append() method. + * @param string the catalogue to add to + * @return boolean true if saved successfuly, false otherwise. + */ + function save($catalogue='messages') + { + $messages = $this->untranslated; + + if(count($messages) <= 0) return false; + + $details = $this->getCatalogueDetails($catalogue); + + if($details) + list($cat_id, $variant, $count) = $details; + else + return false; + + if($cat_id <= 0) return false; + $inserted = 0; + + $time = time(); + + foreach($messages as $message) + { + $count++; $inserted++; + $message = mysql_real_escape_string($message); + $statement = "INSERT INTO trans_unit + (cat_id,id,source,date_added) VALUES + ({$cat_id}, {$count},'{$message}',$time)"; + mysql_query($statement, $this->db); + } + if($inserted > 0) + $this->updateCatalogueTime($cat_id, $variant); + + return $inserted > 0; + } + + /** + * Delete a particular message from the specified catalogue. + * @param string the source message to delete. + * @param string the catalogue to delete from. + * @return boolean true if deleted, false otherwise. + */ + function delete($message, $catalogue='messages') + { + $details = $this->getCatalogueDetails($catalogue); + if($details) + list($cat_id, $variant, $count) = $details; + else + return false; + + $text = mysql_real_escape_string($message); + + $statement = "DELETE FROM trans_unit WHERE + cat_id = {$cat_id} AND source = '{$message}'"; + $deleted = false; + + mysql_query($statement, $this->db); + + if(mysql_affected_rows($this->db) == 1) + $deleted = $this->updateCatalogueTime($cat_id, $variant); + + return $deleted; + + } + + /** + * Update the translation. + * @param string the source string. + * @param string the new translation string. + * @param string comments + * @param string the catalogue of the translation. + * @return boolean true if translation was updated, false otherwise. + */ + function update($text, $target, $comments, $catalogue='messages') + { + $details = $this->getCatalogueDetails($catalogue); + if($details) + list($cat_id, $variant, $count) = $details; + else + return false; + + $comments = mysql_real_escape_string($comments); + $target = mysql_real_escape_string($target); + $text = mysql_real_escape_string($text); + + $time = time(); + + $statement = "UPDATE trans_unit SET + target = '{$target}', + comments = '{$comments}', + date_modified = '{$time}' + WHERE cat_id = {$cat_id} + AND source = '{$text}'"; + + $updated = false; + + mysql_query($statement, $this->db); + if(mysql_affected_rows($this->db) == 1) + $updated = $this->updateCatalogueTime($cat_id, $variant); + + return $updated; + } + + /** + * Returns a list of catalogue as key and all it variants as value. + * @return array list of catalogues + */ + function catalogues() + { + $statement = 'SELECT name FROM catalogue ORDER BY name'; + $rs = mysql_query($statement, $this->db); + $result = array(); + while($row = mysql_fetch_array($rs,MYSQL_NUM)) + { + $details = explode('.',$row[0]); + if(!isset($details[1])) $details[1] = null; + + $result[] = $details; + } + return $result; + } + +} + +?> diff --git a/framework/I18N/core/MessageSource_SQLite.php b/framework/I18N/core/MessageSource_SQLite.php index 22518bba..4694e018 100644 --- a/framework/I18N/core/MessageSource_SQLite.php +++ b/framework/I18N/core/MessageSource_SQLite.php @@ -1,354 +1,354 @@ - - * @version $Revision: 1.4 $ $Date: 2005/02/25 09:59:40 $ - * @package System.I18N.core - */ - -/** - * Get the MessageSource class file. - */ -require_once(dirname(__FILE__).'/MessageSource.php'); - -/** - * Get the I18N utility file, contains the DSN parser. - */ -require_once(dirname(__FILE__).'/util.php'); - -/** - * MessageSource_SQLite class. - * - * Retrive the message translation from a SQLite database. - * - * See the MessageSource::factory() method to instantiate this class. - * - * @author Xiang Wei Zhuo - * @version v1.0, last update on Fri Dec 24 16:58:58 EST 2004 - * @package System.I18N.core - */ -class MessageSource_SQLite extends MessageSource -{ - /** - * The SQLite datasource, the filename of the database. - * @var string - */ - protected $source; - - /** - * Constructor. - * Create a new message source using SQLite. - * @see MessageSource::factory(); - * @param string SQLite datasource, in PEAR's DB DSN format. - */ - function __construct($source) - { - $dsn = parseDSN((string)$source); - $this->source = $dsn['database']; - } - - /** - * Get an array of messages for a particular catalogue and cultural - * variant. - * @param string the catalogue name + variant - * @return array translation messages. - */ - protected function &loadData($variant) - { - $variant = sqlite_escape_string($variant); - - $statement = - "SELECT t.id, t.source, t.target, t.comments - FROM trans_unit t, catalogue c - WHERE c.cat_id = t.cat_id - AND c.name = '{$variant}' - ORDER BY id ASC"; - - $db = sqlite_open($this->source); - $rs = sqlite_query($statement, $db); - - $result = array(); - - while($row = sqlite_fetch_array($rs,SQLITE_NUM)) - { - $source = $row[1]; - $result[$source][] = $row[2]; //target - $result[$source][] = $row[0]; //id - $result[$source][] = $row[3]; //comments - } - - sqlite_close($db); - - return $result; - } - - /** - * Get the last modified unix-time for this particular catalogue+variant. - * We need to query the database to get the date_modified. - * @param string catalogue+variant - * @return int last modified in unix-time format. - */ - protected function getLastModified($source) - { - $source = sqlite_escape_string($source); - - $db = sqlite_open($this->source); - - $rs = sqlite_query( - "SELECT date_modified FROM catalogue WHERE name = '{$source}'", - $db); - - $result = $rs ? (int)sqlite_fetch_single($rs) : 0; - - sqlite_close($db); - - return $result; - } - - /** - * Check if a particular catalogue+variant exists in the database. - * @param string catalogue+variant - * @return boolean true if the catalogue+variant is in the database, - * false otherwise. - */ - protected function isValidSource($variant) - { - $variant = sqlite_escape_string($variant); - $db = sqlite_open($this->source); - $rs = sqlite_query( - "SELECT COUNT(*) FROM catalogue WHERE name = '{$variant}'", - $db); - $result = $rs && (int)sqlite_fetch_single($rs); - sqlite_close($db); - - return $result; - } - - /** - * Get all the variants of a particular catalogue. - * @param string catalogue name - * @return array list of all variants for this catalogue. - */ - protected function getCatalogueList($catalogue) - { - $variants = explode('_',$this->culture); - - $catalogues = array($catalogue); - - $variant = null; - - for($i = 0, $k = count($variants); $i < $k; ++$i) - { - if(isset($variants[$i]{0})) - { - $variant .= ($variant)?'_'.$variants[$i]:$variants[$i]; - $catalogues[] = $catalogue.'.'.$variant; - } - } - return array_reverse($catalogues); - } - - /** - * Retrive catalogue details, array($cat_id, $variant, $count). - * @param string catalogue - * @return array catalogue details, array($cat_id, $variant, $count). - */ - private function getCatalogueDetails($catalogue='messages') - { - if(empty($catalogue)) - $catalogue = 'messages'; - - $variant = $catalogue.'.'.$this->culture; - - $name = sqlite_escape_string($this->getSource($variant)); - - $db = sqlite_open($this->source); - - $rs = sqlite_query("SELECT cat_id - FROM catalogue WHERE name = '{$name}'", $db); - - if(sqlite_num_rows($rs) != 1) - return false; - - $cat_id = (int)sqlite_fetch_single($rs); - - //first get the catalogue ID - $rs = sqlite_query( - "SELECT count(msg_id) - FROM trans_unit - WHERE cat_id = {$cat_id}", $db); - - $count = (int)sqlite_fetch_single($rs); - - sqlite_close($db); - - return array($cat_id, $variant, $count); - } - - /** - * Update the catalogue last modified time. - * @return boolean true if updated, false otherwise. - */ - private function updateCatalogueTime($cat_id, $variant, $db) - { - $time = time(); - - $result = sqlite_query("UPDATE catalogue - SET date_modified = {$time} - WHERE cat_id = {$cat_id}", $db); - - if(!empty($this->cache)) - $this->cache->clean($variant, $this->culture); - - return $result; - } - - /** - * Save the list of untranslated blocks to the translation source. - * If the translation was not found, you should add those - * strings to the translation source via the append() method. - * @param string the catalogue to add to - * @return boolean true if saved successfuly, false otherwise. - */ - function save($catalogue='messages') - { - $messages = $this->untranslated; - - if(count($messages) <= 0) return false; - - $details = $this->getCatalogueDetails($catalogue); - - if($details) - list($cat_id, $variant, $count) = $details; - else - return false; - - if($cat_id <= 0) return false; - $inserted = 0; - - $db = sqlite_open($this->source); - $time = time(); - - foreach($messages as $message) - { - $message = sqlite_escape_string($message); - $statement = "INSERT INTO trans_unit - (cat_id,id,source,date_added) VALUES - ({$cat_id}, {$count},'{$message}',$time)"; - if(sqlite_query($statement, $db)) - { - $count++; $inserted++; - } - } - if($inserted > 0) - $this->updateCatalogueTime($cat_id, $variant, $db); - - sqlite_close($db); - - return $inserted > 0; - } - - /** - * Update the translation. - * @param string the source string. - * @param string the new translation string. - * @param string comments - * @param string the catalogue of the translation. - * @return boolean true if translation was updated, false otherwise. - */ - function update($text, $target, $comments, $catalogue='messages') - { - $details = $this->getCatalogueDetails($catalogue); - if($details) - list($cat_id, $variant, $count) = $details; - else - return false; - - $comments = sqlite_escape_string($comments); - $target = sqlite_escape_string($target); - $text = sqlite_escape_string($text); - - $time = time(); - - $db = sqlite_open($this->source); - - $statement = "UPDATE trans_unit SET - target = '{$target}', - comments = '{$comments}', - date_modified = '{$time}' - WHERE cat_id = {$cat_id} - AND source = '{$text}'"; - - $updated = false; - - if(sqlite_query($statement, $db)) - $updated = $this->updateCatalogueTime($cat_id, $variant, $db); - - sqlite_close($db); - - return $updated; - } - - /** - * Delete a particular message from the specified catalogue. - * @param string the source message to delete. - * @param string the catalogue to delete from. - * @return boolean true if deleted, false otherwise. - */ - function delete($message, $catalogue='messages') - { - $details = $this->getCatalogueDetails($catalogue); - if($details) - list($cat_id, $variant, $count) = $details; - else - return false; - - $db = sqlite_open($this->source); - $text = sqlite_escape_string($message); - - $statement = "DELETE FROM trans_unit WHERE - cat_id = {$cat_id} AND source = '{$message}'"; - $deleted = false; - - if(sqlite_query($statement, $db)) - $deleted = $this->updateCatalogueTime($cat_id, $variant, $db); - - sqlite_close($db); - - return $deleted; - } - - /** - * Returns a list of catalogue as key and all it variants as value. - * @return array list of catalogues - */ - function catalogues() - { - $db = sqlite_open($this->source); - $statement = 'SELECT name FROM catalogue ORDER BY name'; - $rs = sqlite_query($statement, $db); - $result = array(); - while($row = sqlite_fetch_array($rs,SQLITE_NUM)) - { - $details = explode('.',$row[0]); - if(!isset($details[1])) $details[1] = null; - - $result[] = $details; - } - sqlite_close($db); - return $result; - } -} - -?> + + * @version $Revision: 1.4 $ $Date: 2005/02/25 09:59:40 $ + * @package System.I18N.core + */ + +/** + * Get the MessageSource class file. + */ +require_once(dirname(__FILE__).'/MessageSource.php'); + +/** + * Get the I18N utility file, contains the DSN parser. + */ +require_once(dirname(__FILE__).'/util.php'); + +/** + * MessageSource_SQLite class. + * + * Retrive the message translation from a SQLite database. + * + * See the MessageSource::factory() method to instantiate this class. + * + * @author Xiang Wei Zhuo + * @version v1.0, last update on Fri Dec 24 16:58:58 EST 2004 + * @package System.I18N.core + */ +class MessageSource_SQLite extends MessageSource +{ + /** + * The SQLite datasource, the filename of the database. + * @var string + */ + protected $source; + + /** + * Constructor. + * Create a new message source using SQLite. + * @see MessageSource::factory(); + * @param string SQLite datasource, in PEAR's DB DSN format. + */ + function __construct($source) + { + $dsn = parseDSN((string)$source); + $this->source = $dsn['database']; + } + + /** + * Get an array of messages for a particular catalogue and cultural + * variant. + * @param string the catalogue name + variant + * @return array translation messages. + */ + protected function &loadData($variant) + { + $variant = sqlite_escape_string($variant); + + $statement = + "SELECT t.id, t.source, t.target, t.comments + FROM trans_unit t, catalogue c + WHERE c.cat_id = t.cat_id + AND c.name = '{$variant}' + ORDER BY id ASC"; + + $db = sqlite_open($this->source); + $rs = sqlite_query($statement, $db); + + $result = array(); + + while($row = sqlite_fetch_array($rs,SQLITE_NUM)) + { + $source = $row[1]; + $result[$source][] = $row[2]; //target + $result[$source][] = $row[0]; //id + $result[$source][] = $row[3]; //comments + } + + sqlite_close($db); + + return $result; + } + + /** + * Get the last modified unix-time for this particular catalogue+variant. + * We need to query the database to get the date_modified. + * @param string catalogue+variant + * @return int last modified in unix-time format. + */ + protected function getLastModified($source) + { + $source = sqlite_escape_string($source); + + $db = sqlite_open($this->source); + + $rs = sqlite_query( + "SELECT date_modified FROM catalogue WHERE name = '{$source}'", + $db); + + $result = $rs ? (int)sqlite_fetch_single($rs) : 0; + + sqlite_close($db); + + return $result; + } + + /** + * Check if a particular catalogue+variant exists in the database. + * @param string catalogue+variant + * @return boolean true if the catalogue+variant is in the database, + * false otherwise. + */ + protected function isValidSource($variant) + { + $variant = sqlite_escape_string($variant); + $db = sqlite_open($this->source); + $rs = sqlite_query( + "SELECT COUNT(*) FROM catalogue WHERE name = '{$variant}'", + $db); + $result = $rs && (int)sqlite_fetch_single($rs); + sqlite_close($db); + + return $result; + } + + /** + * Get all the variants of a particular catalogue. + * @param string catalogue name + * @return array list of all variants for this catalogue. + */ + protected function getCatalogueList($catalogue) + { + $variants = explode('_',$this->culture); + + $catalogues = array($catalogue); + + $variant = null; + + for($i = 0, $k = count($variants); $i < $k; ++$i) + { + if(isset($variants[$i]{0})) + { + $variant .= ($variant)?'_'.$variants[$i]:$variants[$i]; + $catalogues[] = $catalogue.'.'.$variant; + } + } + return array_reverse($catalogues); + } + + /** + * Retrive catalogue details, array($cat_id, $variant, $count). + * @param string catalogue + * @return array catalogue details, array($cat_id, $variant, $count). + */ + private function getCatalogueDetails($catalogue='messages') + { + if(empty($catalogue)) + $catalogue = 'messages'; + + $variant = $catalogue.'.'.$this->culture; + + $name = sqlite_escape_string($this->getSource($variant)); + + $db = sqlite_open($this->source); + + $rs = sqlite_query("SELECT cat_id + FROM catalogue WHERE name = '{$name}'", $db); + + if(sqlite_num_rows($rs) != 1) + return false; + + $cat_id = (int)sqlite_fetch_single($rs); + + //first get the catalogue ID + $rs = sqlite_query( + "SELECT count(msg_id) + FROM trans_unit + WHERE cat_id = {$cat_id}", $db); + + $count = (int)sqlite_fetch_single($rs); + + sqlite_close($db); + + return array($cat_id, $variant, $count); + } + + /** + * Update the catalogue last modified time. + * @return boolean true if updated, false otherwise. + */ + private function updateCatalogueTime($cat_id, $variant, $db) + { + $time = time(); + + $result = sqlite_query("UPDATE catalogue + SET date_modified = {$time} + WHERE cat_id = {$cat_id}", $db); + + if(!empty($this->cache)) + $this->cache->clean($variant, $this->culture); + + return $result; + } + + /** + * Save the list of untranslated blocks to the translation source. + * If the translation was not found, you should add those + * strings to the translation source via the append() method. + * @param string the catalogue to add to + * @return boolean true if saved successfuly, false otherwise. + */ + function save($catalogue='messages') + { + $messages = $this->untranslated; + + if(count($messages) <= 0) return false; + + $details = $this->getCatalogueDetails($catalogue); + + if($details) + list($cat_id, $variant, $count) = $details; + else + return false; + + if($cat_id <= 0) return false; + $inserted = 0; + + $db = sqlite_open($this->source); + $time = time(); + + foreach($messages as $message) + { + $message = sqlite_escape_string($message); + $statement = "INSERT INTO trans_unit + (cat_id,id,source,date_added) VALUES + ({$cat_id}, {$count},'{$message}',$time)"; + if(sqlite_query($statement, $db)) + { + $count++; $inserted++; + } + } + if($inserted > 0) + $this->updateCatalogueTime($cat_id, $variant, $db); + + sqlite_close($db); + + return $inserted > 0; + } + + /** + * Update the translation. + * @param string the source string. + * @param string the new translation string. + * @param string comments + * @param string the catalogue of the translation. + * @return boolean true if translation was updated, false otherwise. + */ + function update($text, $target, $comments, $catalogue='messages') + { + $details = $this->getCatalogueDetails($catalogue); + if($details) + list($cat_id, $variant, $count) = $details; + else + return false; + + $comments = sqlite_escape_string($comments); + $target = sqlite_escape_string($target); + $text = sqlite_escape_string($text); + + $time = time(); + + $db = sqlite_open($this->source); + + $statement = "UPDATE trans_unit SET + target = '{$target}', + comments = '{$comments}', + date_modified = '{$time}' + WHERE cat_id = {$cat_id} + AND source = '{$text}'"; + + $updated = false; + + if(sqlite_query($statement, $db)) + $updated = $this->updateCatalogueTime($cat_id, $variant, $db); + + sqlite_close($db); + + return $updated; + } + + /** + * Delete a particular message from the specified catalogue. + * @param string the source message to delete. + * @param string the catalogue to delete from. + * @return boolean true if deleted, false otherwise. + */ + function delete($message, $catalogue='messages') + { + $details = $this->getCatalogueDetails($catalogue); + if($details) + list($cat_id, $variant, $count) = $details; + else + return false; + + $db = sqlite_open($this->source); + $text = sqlite_escape_string($message); + + $statement = "DELETE FROM trans_unit WHERE + cat_id = {$cat_id} AND source = '{$message}'"; + $deleted = false; + + if(sqlite_query($statement, $db)) + $deleted = $this->updateCatalogueTime($cat_id, $variant, $db); + + sqlite_close($db); + + return $deleted; + } + + /** + * Returns a list of catalogue as key and all it variants as value. + * @return array list of catalogues + */ + function catalogues() + { + $db = sqlite_open($this->source); + $statement = 'SELECT name FROM catalogue ORDER BY name'; + $rs = sqlite_query($statement, $db); + $result = array(); + while($row = sqlite_fetch_array($rs,SQLITE_NUM)) + { + $details = explode('.',$row[0]); + if(!isset($details[1])) $details[1] = null; + + $result[] = $details; + } + sqlite_close($db); + return $result; + } +} + +?> diff --git a/framework/I18N/core/MessageSource_XLIFF.php b/framework/I18N/core/MessageSource_XLIFF.php index 207fc920..198a1290 100644 --- a/framework/I18N/core/MessageSource_XLIFF.php +++ b/framework/I18N/core/MessageSource_XLIFF.php @@ -1,529 +1,529 @@ - - * @version $Revision: 1.8 $ $Date: 2005/12/17 06:11:28 $ - * @package System.I18N.core - */ - -/** - * Get the MessageSource class file. - */ -require_once(dirname(__FILE__).'/MessageSource.php'); - -/** - * MessageSource_XLIFF class. - * - * Using XML XLIFF format as the message source for translation. - * Details and example of XLIFF can be found in the following URLs. - * - * # http://www.opentag.com/xliff.htm - * # http://www-106.ibm.com/developerworks/xml/library/x-localis2/ - * - * See the MessageSource::factory() method to instantiate this class. - * - * @author Xiang Wei Zhuo - * @version v1.0, last update on Fri Dec 24 16:18:44 EST 2004 - * @package System.I18N.core - */ -class MessageSource_XLIFF extends MessageSource -{ - /** - * Message data filename extension. - * @var string - */ - protected $dataExt = '.xml'; - - /** - * Separator between culture name and source. - * @var string - */ - protected $dataSeparator = '.'; - - /** - * Constructor. - * @param string the directory where the messages are stored. - * @see MessageSource::factory(); - */ - function __construct($source) - { - $this->source = (string)$source; - } - - /** - * Load the messages from a XLIFF file. - * @param string XLIFF file. - * @return array of messages. - */ - protected function &loadData($filename) - { - //load it. - if(false === ($XML = simplexml_load_file($filename))) { - return false; - } - - $translationUnit = $XML->xpath('//trans-unit'); - - $translations = array(); - - foreach($translationUnit as $unit) - { - $source = (string)$unit->source; - $translations[$source][] = (string)$unit->target; - $translations[$source][] = (string)$unit['id']; - $translations[$source][] = (string)$unit->note; - } - - return $translations; - } - - /** - * Get the last modified unix-time for this particular catalogue+variant. - * Just use the file modified time. - * @param string catalogue+variant - * @return int last modified in unix-time format. - */ - protected function getLastModified($source) - { - return is_file($source) ? filemtime($source) : 0; - } - - /** - * Get the XLIFF file for a specific message catalogue and cultural - * vairant. - * @param string message catalogue - * @return string full path to the XLIFF file. - */ - protected function getSource($variant) - { - return $this->source.'/'.$variant; - } - - /** - * Determin if the XLIFF file source is valid. - * @param string XLIFF file - * @return boolean true if valid, false otherwise. - */ - protected function isValidSource($source) - { - return is_file($source); - } - - /** - * Get all the variants of a particular catalogue. - * @param string catalogue name - * @return array list of all variants for this catalogue. - */ - protected function getCatalogueList($catalogue) - { - $variants = explode('_',$this->culture); - $source = $catalogue.$this->dataExt; - $catalogues = array($source); - $variant = null; - - for($i = 0, $k = count($variants); $i < $k; ++$i) - { - if(isset($variants[$i]{0})) - { - $variant .= ($variant)?'_'.$variants[$i]:$variants[$i]; - $catalogues[] = $catalogue.$this->dataSeparator.$variant.$this->dataExt; - } - } - - $byDir = $this->getCatalogueByDir($catalogue); - $catalogues = array_merge($byDir,array_reverse($catalogues)); - $files = array(); - - foreach($catalogues as $file) - { - $files[] = $file; - $files[] = preg_replace('/\.xml$/', '.xlf', $file); - } - - return $files; - } - - /** - * Traverse through the directory structure to find the catalogues. - * This should only be called by getCatalogueList() - * @param string a particular catalogue. - * @return array a list of catalogues. - * @see getCatalogueList() - */ - private function getCatalogueByDir($catalogue) - { - $variants = explode('_',$this->culture); - $catalogues = array(); - $variant = null; - - for($i = 0, $k = count($variants); $i < $k; ++$i) - { - if(isset($variants[$i]{0})) - { - $variant .= ($variant)?'_'.$variants[$i]:$variants[$i]; - $catalogues[] = $variant.'/'.$catalogue.$this->dataExt; - } - } - - return array_reverse($catalogues); - } - - /** - * Returns a list of catalogue and its culture ID. - * E.g. array('messages','en_AU') - * @return array list of catalogues - * @see getCatalogues() - */ - public function catalogues() - { - return $this->getCatalogues(); - } - - /** - * Returns a list of catalogue and its culture ID. This takes care - * of directory structures. - * E.g. array('messages','en_AU') - * @return array list of catalogues - */ - protected function getCatalogues($dir=null,$variant=null) - { - $dir = $dir?$dir:$this->source; - $files = scandir($dir); - $catalogue = array(); - - foreach($files as $file) - { - if(is_dir($dir.'/'.$file) && preg_match('/^[a-z]{2}(_[A-Z]{2,3})?$/',$file)) { - $catalogue = array_merge( - $catalogue, - $this->getCatalogues($dir.'/'.$file, $file) - ); - } - - $pos = strpos($file,$this->dataExt); - if($pos >0 && substr($file, -1*strlen($this->dataExt)) == $this->dataExt) - { - $name = substr($file,0,$pos); - $dot = strrpos($name,$this->dataSeparator); - $culture = $variant; - $cat = $name; - - if(is_int($dot)) - { - $culture = substr($name, $dot+1, strlen($name)); - $cat = substr($name, 0, $dot); - } - - $details[0] = $cat; - $details[1] = $culture; - $catalogue[] = $details; - } - } - sort($catalogue); - return $catalogue; - } - - /** - * Get the variant for a catalogue depending on the current culture. - * @param string catalogue - * @return string the variant. - * @see save() - * @see update() - * @see delete() - */ - private function getVariants($catalogue='messages') - { - if($catalogue === null) { - $catalogue = 'messages'; - } - - foreach($this->getCatalogueList($catalogue) as $variant) - { - $file = $this->getSource($variant); - if(is_file($file)) { - return array($variant, $file); - } - } - return false; - } - - /** - * Save the list of untranslated blocks to the translation source. - * If the translation was not found, you should add those - * strings to the translation source via the append() method. - * @param string the catalogue to add to - * @return boolean true if saved successfuly, false otherwise. - */ - public function save($catalogue='messages') - { - $messages = $this->untranslated; - if(count($messages) <= 0) { - return false; - } - - $variants = $this->getVariants($catalogue); - - if($variants) { - list($variant, $filename) = $variants; - } else { - list($variant, $filename) = $this->createMessageTemplate($catalogue); - } - - if(is_writable($filename) == false) { - throw new TIOException("Unable to save to file {$filename}, file must be writable."); - } - - //create a new dom, import the existing xml - $dom = new DOMDocument(); - $dom->load($filename); - - //find the body element - $xpath = new DomXPath($dom); - $body = $xpath->query('//body')->item(0); - - $lastNodes = $xpath->query('//trans-unit[last()]'); - if(($last=$lastNodes->item(0))!==null) { - $count = (int)$last->getAttribute('id'); - } else { - $count = 0; - } - - //for each message add it to the XML file using DOM - foreach($messages as $message) - { - $unit = $dom->createElement('trans-unit'); - $unit->setAttribute('id',++$count); - - $source = $dom->createElement('source'); - $source->appendChild($dom->createCDATASection($message)); - - $target = $dom->createElement('target'); - $target->appendChild($dom->createCDATASection('')); - - $unit->appendChild($dom->createTextNode("\n")); - $unit->appendChild($source); - $unit->appendChild($dom->createTextNode("\n")); - $unit->appendChild($target); - $unit->appendChild($dom->createTextNode("\n")); - - $body->appendChild($dom->createTextNode("\n")); - $body->appendChild($unit); - $body->appendChild($dom->createTextNode("\n")); - } - - - $fileNode = $xpath->query('//file')->item(0); - $fileNode->setAttribute('date', @date('Y-m-d\TH:i:s\Z')); - - //save it and clear the cache for this variant - $dom->save($filename); - if(!empty($this->cache)) { - $this->cache->clean($variant, $this->culture); - } - - return true; - } - - /** - * Update the translation. - * @param string the source string. - * @param string the new translation string. - * @param string comments - * @param string the catalogue to save to. - * @return boolean true if translation was updated, false otherwise. - */ - public function update($text, $target, $comments, $catalogue='messages') - { - $variants = $this->getVariants($catalogue); - - if($variants) { - list($variant, $filename) = $variants; - } else { - return false; - } - - if(is_writable($filename) == false) { - throw new TIOException("Unable to update file {$filename}, file must be writable."); - } - - //create a new dom, import the existing xml - $dom = DOMDocument::load($filename); - - //find the body element - $xpath = new DomXPath($dom); - $units = $xpath->query('//trans-unit'); - - //for each of the existin units - foreach($units as $unit) - { - $found = false; - $targetted = false; - $commented = false; - - //in each unit, need to find the source, target and comment nodes - //it will assume that the source is before the target. - foreach($unit->childNodes as $node) - { - //source node - if($node->nodeName == 'source' && $node->firstChild->wholeText == $text) { - $found = true; - } - - //found source, get the target and notes - if($found) - { - //set the new translated string - if($node->nodeName == 'target') - { - $node->nodeValue = $target; - $targetted = true; - } - - //set the notes - if(!empty($comments) && $node->nodeName == 'note') - { - $node->nodeValue = $comments; - $commented = true; - } - } - } - - //append a target - if($found && !$targetted) { - $unit->appendChild($dom->createElement('target',$target)); - } - - //append a note - if($found && !$commented && !empty($comments)) { - $unit->appendChild($dom->createElement('note',$comments)); - } - - //finished searching - if($found) { - break; - } - } - - $fileNode = $xpath->query('//file')->item(0); - $fileNode->setAttribute('date', @date('Y-m-d\TH:i:s\Z')); - - if($dom->save($filename) >0) - { - if(!empty($this->cache)) { - $this->cache->clean($variant, $this->culture); - } - - return true; - } - - return false; - } - - /** - * Delete a particular message from the specified catalogue. - * @param string the source message to delete. - * @param string the catalogue to delete from. - * @return boolean true if deleted, false otherwise. - */ - public function delete($message, $catalogue='messages') - { - $variants = $this->getVariants($catalogue); - if($variants) { - list($variant, $filename) = $variants; - } else { - return false; - } - - if(is_writable($filename) == false) { - throw new TIOException("Unable to modify file {$filename}, file must be writable."); - } - - //create a new dom, import the existing xml - $dom = DOMDocument::load($filename); - - //find the body element - $xpath = new DomXPath($dom); - $units = $xpath->query('//trans-unit'); - - //for each of the existin units - foreach($units as $unit) - { - //in each unit, need to find the source, target and comment nodes - //it will assume that the source is before the target. - foreach($unit->childNodes as $node) - { - //source node - if($node->nodeName == 'source' && $node->firstChild->wholeText == $message) - { - //we found it, remove and save the xml file. - $unit->parentNode->removeChild($unit); - $fileNode = $xpath->query('//file')->item(0); - $fileNode->setAttribute('date', @date('Y-m-d\TH:i:s\Z')); - - if(false !== $dom->save($filename)) { - if(!empty($this->cache)) { - $this->cache->clean($variant, $this->culture); - } - return true; - } - - return false; - } - } - } - - return false; - } - - protected function createMessageTemplate($catalogue) - { - if($catalogue === null) { - $catalogue = 'messages'; - } - - $variants = $this->getCatalogueList($catalogue); - $variant = array_shift($variants); - $file = $this->getSource($variant); - $dir = dirname($file); - - if(!is_dir($dir)) { - @mkdir($dir); - @chmod($dir,PRADO_CHMOD); - } - - if(!is_dir($dir)) { - throw new TException("Unable to create directory $dir"); - } - - file_put_contents($file, $this->getTemplate($catalogue)); - chmod($file, PRADO_CHMOD); - - return array($variant, $file); - } - - protected function getTemplate($catalogue) - { - $date = @date('c'); - $xml = << - - - - - - -EOD; - return $xml; - } -} + + * @version $Revision: 1.8 $ $Date: 2005/12/17 06:11:28 $ + * @package System.I18N.core + */ + +/** + * Get the MessageSource class file. + */ +require_once(dirname(__FILE__).'/MessageSource.php'); + +/** + * MessageSource_XLIFF class. + * + * Using XML XLIFF format as the message source for translation. + * Details and example of XLIFF can be found in the following URLs. + * + * # http://www.opentag.com/xliff.htm + * # http://www-106.ibm.com/developerworks/xml/library/x-localis2/ + * + * See the MessageSource::factory() method to instantiate this class. + * + * @author Xiang Wei Zhuo + * @version v1.0, last update on Fri Dec 24 16:18:44 EST 2004 + * @package System.I18N.core + */ +class MessageSource_XLIFF extends MessageSource +{ + /** + * Message data filename extension. + * @var string + */ + protected $dataExt = '.xml'; + + /** + * Separator between culture name and source. + * @var string + */ + protected $dataSeparator = '.'; + + /** + * Constructor. + * @param string the directory where the messages are stored. + * @see MessageSource::factory(); + */ + function __construct($source) + { + $this->source = (string)$source; + } + + /** + * Load the messages from a XLIFF file. + * @param string XLIFF file. + * @return array of messages. + */ + protected function &loadData($filename) + { + //load it. + if(false === ($XML = simplexml_load_file($filename))) { + return false; + } + + $translationUnit = $XML->xpath('//trans-unit'); + + $translations = array(); + + foreach($translationUnit as $unit) + { + $source = (string)$unit->source; + $translations[$source][] = (string)$unit->target; + $translations[$source][] = (string)$unit['id']; + $translations[$source][] = (string)$unit->note; + } + + return $translations; + } + + /** + * Get the last modified unix-time for this particular catalogue+variant. + * Just use the file modified time. + * @param string catalogue+variant + * @return int last modified in unix-time format. + */ + protected function getLastModified($source) + { + return is_file($source) ? filemtime($source) : 0; + } + + /** + * Get the XLIFF file for a specific message catalogue and cultural + * vairant. + * @param string message catalogue + * @return string full path to the XLIFF file. + */ + protected function getSource($variant) + { + return $this->source.'/'.$variant; + } + + /** + * Determin if the XLIFF file source is valid. + * @param string XLIFF file + * @return boolean true if valid, false otherwise. + */ + protected function isValidSource($source) + { + return is_file($source); + } + + /** + * Get all the variants of a particular catalogue. + * @param string catalogue name + * @return array list of all variants for this catalogue. + */ + protected function getCatalogueList($catalogue) + { + $variants = explode('_',$this->culture); + $source = $catalogue.$this->dataExt; + $catalogues = array($source); + $variant = null; + + for($i = 0, $k = count($variants); $i < $k; ++$i) + { + if(isset($variants[$i]{0})) + { + $variant .= ($variant)?'_'.$variants[$i]:$variants[$i]; + $catalogues[] = $catalogue.$this->dataSeparator.$variant.$this->dataExt; + } + } + + $byDir = $this->getCatalogueByDir($catalogue); + $catalogues = array_merge($byDir,array_reverse($catalogues)); + $files = array(); + + foreach($catalogues as $file) + { + $files[] = $file; + $files[] = preg_replace('/\.xml$/', '.xlf', $file); + } + + return $files; + } + + /** + * Traverse through the directory structure to find the catalogues. + * This should only be called by getCatalogueList() + * @param string a particular catalogue. + * @return array a list of catalogues. + * @see getCatalogueList() + */ + private function getCatalogueByDir($catalogue) + { + $variants = explode('_',$this->culture); + $catalogues = array(); + $variant = null; + + for($i = 0, $k = count($variants); $i < $k; ++$i) + { + if(isset($variants[$i]{0})) + { + $variant .= ($variant)?'_'.$variants[$i]:$variants[$i]; + $catalogues[] = $variant.'/'.$catalogue.$this->dataExt; + } + } + + return array_reverse($catalogues); + } + + /** + * Returns a list of catalogue and its culture ID. + * E.g. array('messages','en_AU') + * @return array list of catalogues + * @see getCatalogues() + */ + public function catalogues() + { + return $this->getCatalogues(); + } + + /** + * Returns a list of catalogue and its culture ID. This takes care + * of directory structures. + * E.g. array('messages','en_AU') + * @return array list of catalogues + */ + protected function getCatalogues($dir=null,$variant=null) + { + $dir = $dir?$dir:$this->source; + $files = scandir($dir); + $catalogue = array(); + + foreach($files as $file) + { + if(is_dir($dir.'/'.$file) && preg_match('/^[a-z]{2}(_[A-Z]{2,3})?$/',$file)) { + $catalogue = array_merge( + $catalogue, + $this->getCatalogues($dir.'/'.$file, $file) + ); + } + + $pos = strpos($file,$this->dataExt); + if($pos >0 && substr($file, -1*strlen($this->dataExt)) == $this->dataExt) + { + $name = substr($file,0,$pos); + $dot = strrpos($name,$this->dataSeparator); + $culture = $variant; + $cat = $name; + + if(is_int($dot)) + { + $culture = substr($name, $dot+1, strlen($name)); + $cat = substr($name, 0, $dot); + } + + $details[0] = $cat; + $details[1] = $culture; + $catalogue[] = $details; + } + } + sort($catalogue); + return $catalogue; + } + + /** + * Get the variant for a catalogue depending on the current culture. + * @param string catalogue + * @return string the variant. + * @see save() + * @see update() + * @see delete() + */ + private function getVariants($catalogue='messages') + { + if($catalogue === null) { + $catalogue = 'messages'; + } + + foreach($this->getCatalogueList($catalogue) as $variant) + { + $file = $this->getSource($variant); + if(is_file($file)) { + return array($variant, $file); + } + } + return false; + } + + /** + * Save the list of untranslated blocks to the translation source. + * If the translation was not found, you should add those + * strings to the translation source via the append() method. + * @param string the catalogue to add to + * @return boolean true if saved successfuly, false otherwise. + */ + public function save($catalogue='messages') + { + $messages = $this->untranslated; + if(count($messages) <= 0) { + return false; + } + + $variants = $this->getVariants($catalogue); + + if($variants) { + list($variant, $filename) = $variants; + } else { + list($variant, $filename) = $this->createMessageTemplate($catalogue); + } + + if(is_writable($filename) == false) { + throw new TIOException("Unable to save to file {$filename}, file must be writable."); + } + + //create a new dom, import the existing xml + $dom = new DOMDocument(); + $dom->load($filename); + + //find the body element + $xpath = new DomXPath($dom); + $body = $xpath->query('//body')->item(0); + + $lastNodes = $xpath->query('//trans-unit[last()]'); + if(($last=$lastNodes->item(0))!==null) { + $count = (int)$last->getAttribute('id'); + } else { + $count = 0; + } + + //for each message add it to the XML file using DOM + foreach($messages as $message) + { + $unit = $dom->createElement('trans-unit'); + $unit->setAttribute('id',++$count); + + $source = $dom->createElement('source'); + $source->appendChild($dom->createCDATASection($message)); + + $target = $dom->createElement('target'); + $target->appendChild($dom->createCDATASection('')); + + $unit->appendChild($dom->createTextNode("\n")); + $unit->appendChild($source); + $unit->appendChild($dom->createTextNode("\n")); + $unit->appendChild($target); + $unit->appendChild($dom->createTextNode("\n")); + + $body->appendChild($dom->createTextNode("\n")); + $body->appendChild($unit); + $body->appendChild($dom->createTextNode("\n")); + } + + + $fileNode = $xpath->query('//file')->item(0); + $fileNode->setAttribute('date', @date('Y-m-d\TH:i:s\Z')); + + //save it and clear the cache for this variant + $dom->save($filename); + if(!empty($this->cache)) { + $this->cache->clean($variant, $this->culture); + } + + return true; + } + + /** + * Update the translation. + * @param string the source string. + * @param string the new translation string. + * @param string comments + * @param string the catalogue to save to. + * @return boolean true if translation was updated, false otherwise. + */ + public function update($text, $target, $comments, $catalogue='messages') + { + $variants = $this->getVariants($catalogue); + + if($variants) { + list($variant, $filename) = $variants; + } else { + return false; + } + + if(is_writable($filename) == false) { + throw new TIOException("Unable to update file {$filename}, file must be writable."); + } + + //create a new dom, import the existing xml + $dom = DOMDocument::load($filename); + + //find the body element + $xpath = new DomXPath($dom); + $units = $xpath->query('//trans-unit'); + + //for each of the existin units + foreach($units as $unit) + { + $found = false; + $targetted = false; + $commented = false; + + //in each unit, need to find the source, target and comment nodes + //it will assume that the source is before the target. + foreach($unit->childNodes as $node) + { + //source node + if($node->nodeName == 'source' && $node->firstChild->wholeText == $text) { + $found = true; + } + + //found source, get the target and notes + if($found) + { + //set the new translated string + if($node->nodeName == 'target') + { + $node->nodeValue = $target; + $targetted = true; + } + + //set the notes + if(!empty($comments) && $node->nodeName == 'note') + { + $node->nodeValue = $comments; + $commented = true; + } + } + } + + //append a target + if($found && !$targetted) { + $unit->appendChild($dom->createElement('target',$target)); + } + + //append a note + if($found && !$commented && !empty($comments)) { + $unit->appendChild($dom->createElement('note',$comments)); + } + + //finished searching + if($found) { + break; + } + } + + $fileNode = $xpath->query('//file')->item(0); + $fileNode->setAttribute('date', @date('Y-m-d\TH:i:s\Z')); + + if($dom->save($filename) >0) + { + if(!empty($this->cache)) { + $this->cache->clean($variant, $this->culture); + } + + return true; + } + + return false; + } + + /** + * Delete a particular message from the specified catalogue. + * @param string the source message to delete. + * @param string the catalogue to delete from. + * @return boolean true if deleted, false otherwise. + */ + public function delete($message, $catalogue='messages') + { + $variants = $this->getVariants($catalogue); + if($variants) { + list($variant, $filename) = $variants; + } else { + return false; + } + + if(is_writable($filename) == false) { + throw new TIOException("Unable to modify file {$filename}, file must be writable."); + } + + //create a new dom, import the existing xml + $dom = DOMDocument::load($filename); + + //find the body element + $xpath = new DomXPath($dom); + $units = $xpath->query('//trans-unit'); + + //for each of the existin units + foreach($units as $unit) + { + //in each unit, need to find the source, target and comment nodes + //it will assume that the source is before the target. + foreach($unit->childNodes as $node) + { + //source node + if($node->nodeName == 'source' && $node->firstChild->wholeText == $message) + { + //we found it, remove and save the xml file. + $unit->parentNode->removeChild($unit); + $fileNode = $xpath->query('//file')->item(0); + $fileNode->setAttribute('date', @date('Y-m-d\TH:i:s\Z')); + + if(false !== $dom->save($filename)) { + if(!empty($this->cache)) { + $this->cache->clean($variant, $this->culture); + } + return true; + } + + return false; + } + } + } + + return false; + } + + protected function createMessageTemplate($catalogue) + { + if($catalogue === null) { + $catalogue = 'messages'; + } + + $variants = $this->getCatalogueList($catalogue); + $variant = array_shift($variants); + $file = $this->getSource($variant); + $dir = dirname($file); + + if(!is_dir($dir)) { + @mkdir($dir); + @chmod($dir,PRADO_CHMOD); + } + + if(!is_dir($dir)) { + throw new TException("Unable to create directory $dir"); + } + + file_put_contents($file, $this->getTemplate($catalogue)); + chmod($file, PRADO_CHMOD); + + return array($variant, $file); + } + + protected function getTemplate($catalogue) + { + $date = @date('c'); + $xml = << + + + + + + +EOD; + return $xml; + } +} diff --git a/framework/I18N/core/MessageSource_gettext.php b/framework/I18N/core/MessageSource_gettext.php index 5428e32b..0e12ddba 100644 --- a/framework/I18N/core/MessageSource_gettext.php +++ b/framework/I18N/core/MessageSource_gettext.php @@ -1,457 +1,457 @@ - - * @version $Revision: 1.7 $ $Date: 2005/12/17 06:11:28 $ - * @package System.I18N.core - */ - -/** - * Get the MessageSource class file. - */ -require_once(dirname(__FILE__).'/MessageSource.php'); - -/** - * Get the Gettext class. - */ -require_once(dirname(__FILE__).'/Gettext/TGettext.php'); - -/** - * MessageSource_gettext class. - * - * Using Gettext MO format as the message source for translation. - * The gettext classes are based on PEAR's gettext MO and PO classes. - * - * See the MessageSource::factory() method to instantiate this class. - * - * @author Xiang Wei Zhuo - * @version v1.0, last update on Fri Dec 24 16:18:44 EST 2004 - * @package System.I18N.core - */ -class MessageSource_gettext extends MessageSource -{ - /** - * Message data filename extension. - * @var string - */ - protected $dataExt = '.mo'; - - /** - * PO data filename extension - * @var string - */ - protected $poExt = '.po'; - - /** - * Separator between culture name and source. - * @var string - */ - protected $dataSeparator = '.'; - - function __construct($source) - { - $this->source = (string)$source; - } - - - /** - * Load the messages from a MO file. - * @param string MO file. - * @return array of messages. - */ - protected function &loadData($filename) - { - $mo = TGettext::factory('MO',$filename); - $mo->load(); - $result = $mo->toArray(); - - $results = array(); - $count=0; - foreach($result['strings'] as $source => $target) - { - $results[$source][] = $target; //target - $results[$source][] = $count++; //id - $results[$source][] = ''; //comments - } - return $results; - } - - /** - * Determin if the MO file source is valid. - * @param string MO file - * @return boolean true if valid, false otherwise. - */ - protected function isValidSource($filename) - { - return is_file($filename); - } - - /** - * Get the MO file for a specific message catalogue and cultural - * vairant. - * @param string message catalogue - * @return string full path to the MO file. - */ - protected function getSource($variant) - { - return $this->source.'/'.$variant; - } - - /** - * Get the last modified unix-time for this particular catalogue+variant. - * Just use the file modified time. - * @param string catalogue+variant - * @return int last modified in unix-time format. - */ - protected function getLastModified($source) - { - if(is_file($source)) - return filemtime($source); - else - return 0; - } - - /** - * Get all the variants of a particular catalogue. - * @param string catalogue name - * @return array list of all variants for this catalogue. - */ - protected function getCatalogueList($catalogue) - { - $variants = explode('_',$this->culture); - $source = $catalogue.$this->dataExt; - - $catalogues = array($source); - - $variant = null; - - for($i = 0, $k = count($variants); $i < $k; ++$i) - { - if(isset($variants[$i]{0})) - { - $variant .= ($variant)?'_'.$variants[$i]:$variants[$i]; - $catalogues[] = $catalogue.$this->dataSeparator. - $variant.$this->dataExt; - } - } - $byDir = $this->getCatalogueByDir($catalogue); - $catalogues = array_merge($byDir,array_reverse($catalogues)); - return $catalogues; - } - - - /** - * Traverse through the directory structure to find the catalogues. - * This should only be called by getCatalogueList() - * @param string a particular catalogue. - * @return array a list of catalogues. - * @see getCatalogueList() - */ - private function getCatalogueByDir($catalogue) - { - $variants = explode('_',$this->culture); - $catalogues = array(); - - $variant = null; - - for($i = 0, $k = count($variants); $i < $k; ++$i) - { - if(isset($variants[$i]{0})) - { - $variant .= ($variant)?'_'.$variants[$i]:$variants[$i]; - $catalogues[] = $variant.'/'.$catalogue.$this->dataExt; - } - } - return array_reverse($catalogues); - } - - /** - * Get the variant for a catalogue depending on the current culture. - * @param string catalogue - * @return string the variant. - * @see save() - * @see update() - * @see delete() - */ - private function getVariants($catalogue='messages') - { - if($catalogue === null) { - $catalogue = 'messages'; - } - - foreach($this->getCatalogueList($catalogue) as $variant) - { - $file = $this->getSource($variant); - $po = $this->getPOFile($file); - if(is_file($file) || is_file($po)) - return array($variant, $file, $po); - } - return false; - } - - private function getPOFile($MOFile) - { - $filebase = substr($MOFile, 0, strlen($MOFile)-strlen($this->dataExt)); - return $filebase.$this->poExt; - } - - /** - * Save the list of untranslated blocks to the translation source. - * If the translation was not found, you should add those - * strings to the translation source via the append() method. - * @param string the catalogue to add to - * @return boolean true if saved successfuly, false otherwise. - */ - function save($catalogue='messages') - { - $messages = $this->untranslated; - - if(count($messages) <= 0) return false; - - $variants = $this->getVariants($catalogue); - - if($variants) - list($variant, $MOFile, $POFile) = $variants; - else - list($variant, $MOFile, $POFile) = $this->createMessageTemplate($catalogue); - - if(is_writable($MOFile) == false) - throw new TIOException("Unable to save to file {$MOFile}, file must be writable."); - if(is_writable($POFile) == false) - throw new TIOException("Unable to save to file {$POFile}, file must be writable."); - - //set the strings as untranslated. - $strings = array(); - foreach($messages as $message) - $strings[$message] = ''; - - //load the PO - $po = TGettext::factory('PO',$POFile); - $po->load(); - $result = $po->toArray(); - - $existing = count($result['strings']); - - //add to strings to the existing message list - $result['strings'] = array_merge($result['strings'],$strings); - - $new = count($result['strings']); - - if($new > $existing) - { - //change the date 2004-12-25 12:26 - $result['meta']['PO-Revision-Date'] = @date('Y-m-d H:i:s'); - - $po->fromArray($result); - $mo = $po->toMO(); - if($po->save() && $mo->save($MOFile)) - { - if(!empty($this->cache)) - $this->cache->clean($variant, $this->culture); - return true; - } - else - return false; - } - return false; - } - - /** - * Delete a particular message from the specified catalogue. - * @param string the source message to delete. - * @param string the catalogue to delete from. - * @return boolean true if deleted, false otherwise. - */ - function delete($message, $catalogue='messages') - { - $variants = $this->getVariants($catalogue); - if($variants) - list($variant, $MOFile, $POFile) = $variants; - else - return false; - - if(is_writable($MOFile) == false) - throw new TIOException("Unable to modify file {$MOFile}, file must be writable."); - if(is_writable($POFile) == false) - throw new TIOException("Unable to modify file {$POFile}, file must be writable."); - - $po = TGettext::factory('PO',$POFile); - $po->load(); - $result = $po->toArray(); - - foreach($result['strings'] as $string => $value) - { - if($string == $message) - { - $result['meta']['PO-Revision-Date'] = @date('Y-m-d H:i:s'); - unset($result['strings'][$string]); - - $po->fromArray($result); - $mo = $po->toMO(); - if($po->save() && $mo->save($MOFile)) - { - if(!empty($this->cache)) - $this->cache->clean($variant, $this->culture); - return true; - } - else - return false; - } - } - - return false; - } - - /** - * Update the translation. - * @param string the source string. - * @param string the new translation string. - * @param string comments - * @param string the catalogue of the translation. - * @return boolean true if translation was updated, false otherwise. - */ - function update($text, $target, $comments, $catalogue='messages') - { - $variants = $this->getVariants($catalogue); - if($variants) - list($variant, $MOFile, $POFile) = $variants; - else - return false; - - if(is_writable($MOFile) == false) - throw new TIOException("Unable to update file {$MOFile}, file must be writable."); - if(is_writable($POFile) == false) - throw new TIOException("Unable to update file {$POFile}, file must be writable."); - - - $po = TGettext::factory('PO',$POFile); - $po->load(); - $result = $po->toArray(); - - foreach($result['strings'] as $string => $value) - { - if($string == $text) - { - $result['strings'][$string] = $target; - $result['meta']['PO-Revision-Date'] = @date('Y-m-d H:i:s'); - - $po->fromArray($result); - $mo = $po->toMO(); - - if($po->save() && $mo->save($MOFile)) - { - if(!empty($this->cache)) - $this->cache->clean($variant, $this->culture); - return true; - } - else - return false; - } - } - - return false; - } - - - /** - * Returns a list of catalogue as key and all it variants as value. - * @return array list of catalogues - */ - function catalogues() - { - return $this->getCatalogues(); - } - - /** - * Returns a list of catalogue and its culture ID. This takes care - * of directory structures. - * E.g. array('messages','en_AU') - * @return array list of catalogues - */ - protected function getCatalogues($dir=null,$variant=null) - { - $dir = $dir?$dir:$this->source; - $files = scandir($dir); - - $catalogue = array(); - - foreach($files as $file) - { - if(is_dir($dir.'/'.$file) - && preg_match('/^[a-z]{2}(_[A-Z]{2,3})?$/',$file)) - { - - $catalogue = array_merge($catalogue, - $this->getCatalogues($dir.'/'.$file, $file)); - } - - $pos = strpos($file,$this->dataExt); - - if($pos >0 - && substr($file,-1*strlen($this->dataExt)) == $this->dataExt) - { - $name = substr($file,0,$pos); - $dot = strrpos($name,$this->dataSeparator); - $culture = $variant; - $cat = $name; - if(is_int($dot)) - { - $culture = substr($name, $dot+1,strlen($name)); - $cat = substr($name,0,$dot); - } - $details[0] = $cat; - $details[1] = $culture; - - - $catalogue[] = $details; - } - } - sort($catalogue); - - return $catalogue; - } - - protected function createMessageTemplate($catalogue) - { - if($catalogue === null) { - $catalogue = 'messages'; - } - $variants = $this->getCatalogueList($catalogue); - $variant = array_shift($variants); - $mo_file = $this->getSource($variant); - $po_file = $this->getPOFile($mo_file); - - $dir = dirname($mo_file); - if(!is_dir($dir)) - { - @mkdir($dir); - @chmod($dir,PRADO_CHMOD); - } - if(!is_dir($dir)) - throw new TException("Unable to create directory $dir"); - - $po = TGettext::factory('PO',$po_file); - $result['meta']['PO-Revision-Date'] = @date('Y-m-d H:i:s'); - $result['strings'] = array(); - - $po->fromArray($result); - $mo = $po->toMO(); - if($po->save() && $mo->save($mo_file)) - return array($variant, $mo_file, $po_file); - else - throw new TException("Unable to create file $po_file and $mo_file"); - } -} - -?> + + * @version $Revision: 1.7 $ $Date: 2005/12/17 06:11:28 $ + * @package System.I18N.core + */ + +/** + * Get the MessageSource class file. + */ +require_once(dirname(__FILE__).'/MessageSource.php'); + +/** + * Get the Gettext class. + */ +require_once(dirname(__FILE__).'/Gettext/TGettext.php'); + +/** + * MessageSource_gettext class. + * + * Using Gettext MO format as the message source for translation. + * The gettext classes are based on PEAR's gettext MO and PO classes. + * + * See the MessageSource::factory() method to instantiate this class. + * + * @author Xiang Wei Zhuo + * @version v1.0, last update on Fri Dec 24 16:18:44 EST 2004 + * @package System.I18N.core + */ +class MessageSource_gettext extends MessageSource +{ + /** + * Message data filename extension. + * @var string + */ + protected $dataExt = '.mo'; + + /** + * PO data filename extension + * @var string + */ + protected $poExt = '.po'; + + /** + * Separator between culture name and source. + * @var string + */ + protected $dataSeparator = '.'; + + function __construct($source) + { + $this->source = (string)$source; + } + + + /** + * Load the messages from a MO file. + * @param string MO file. + * @return array of messages. + */ + protected function &loadData($filename) + { + $mo = TGettext::factory('MO',$filename); + $mo->load(); + $result = $mo->toArray(); + + $results = array(); + $count=0; + foreach($result['strings'] as $source => $target) + { + $results[$source][] = $target; //target + $results[$source][] = $count++; //id + $results[$source][] = ''; //comments + } + return $results; + } + + /** + * Determin if the MO file source is valid. + * @param string MO file + * @return boolean true if valid, false otherwise. + */ + protected function isValidSource($filename) + { + return is_file($filename); + } + + /** + * Get the MO file for a specific message catalogue and cultural + * vairant. + * @param string message catalogue + * @return string full path to the MO file. + */ + protected function getSource($variant) + { + return $this->source.'/'.$variant; + } + + /** + * Get the last modified unix-time for this particular catalogue+variant. + * Just use the file modified time. + * @param string catalogue+variant + * @return int last modified in unix-time format. + */ + protected function getLastModified($source) + { + if(is_file($source)) + return filemtime($source); + else + return 0; + } + + /** + * Get all the variants of a particular catalogue. + * @param string catalogue name + * @return array list of all variants for this catalogue. + */ + protected function getCatalogueList($catalogue) + { + $variants = explode('_',$this->culture); + $source = $catalogue.$this->dataExt; + + $catalogues = array($source); + + $variant = null; + + for($i = 0, $k = count($variants); $i < $k; ++$i) + { + if(isset($variants[$i]{0})) + { + $variant .= ($variant)?'_'.$variants[$i]:$variants[$i]; + $catalogues[] = $catalogue.$this->dataSeparator. + $variant.$this->dataExt; + } + } + $byDir = $this->getCatalogueByDir($catalogue); + $catalogues = array_merge($byDir,array_reverse($catalogues)); + return $catalogues; + } + + + /** + * Traverse through the directory structure to find the catalogues. + * This should only be called by getCatalogueList() + * @param string a particular catalogue. + * @return array a list of catalogues. + * @see getCatalogueList() + */ + private function getCatalogueByDir($catalogue) + { + $variants = explode('_',$this->culture); + $catalogues = array(); + + $variant = null; + + for($i = 0, $k = count($variants); $i < $k; ++$i) + { + if(isset($variants[$i]{0})) + { + $variant .= ($variant)?'_'.$variants[$i]:$variants[$i]; + $catalogues[] = $variant.'/'.$catalogue.$this->dataExt; + } + } + return array_reverse($catalogues); + } + + /** + * Get the variant for a catalogue depending on the current culture. + * @param string catalogue + * @return string the variant. + * @see save() + * @see update() + * @see delete() + */ + private function getVariants($catalogue='messages') + { + if($catalogue === null) { + $catalogue = 'messages'; + } + + foreach($this->getCatalogueList($catalogue) as $variant) + { + $file = $this->getSource($variant); + $po = $this->getPOFile($file); + if(is_file($file) || is_file($po)) + return array($variant, $file, $po); + } + return false; + } + + private function getPOFile($MOFile) + { + $filebase = substr($MOFile, 0, strlen($MOFile)-strlen($this->dataExt)); + return $filebase.$this->poExt; + } + + /** + * Save the list of untranslated blocks to the translation source. + * If the translation was not found, you should add those + * strings to the translation source via the append() method. + * @param string the catalogue to add to + * @return boolean true if saved successfuly, false otherwise. + */ + function save($catalogue='messages') + { + $messages = $this->untranslated; + + if(count($messages) <= 0) return false; + + $variants = $this->getVariants($catalogue); + + if($variants) + list($variant, $MOFile, $POFile) = $variants; + else + list($variant, $MOFile, $POFile) = $this->createMessageTemplate($catalogue); + + if(is_writable($MOFile) == false) + throw new TIOException("Unable to save to file {$MOFile}, file must be writable."); + if(is_writable($POFile) == false) + throw new TIOException("Unable to save to file {$POFile}, file must be writable."); + + //set the strings as untranslated. + $strings = array(); + foreach($messages as $message) + $strings[$message] = ''; + + //load the PO + $po = TGettext::factory('PO',$POFile); + $po->load(); + $result = $po->toArray(); + + $existing = count($result['strings']); + + //add to strings to the existing message list + $result['strings'] = array_merge($result['strings'],$strings); + + $new = count($result['strings']); + + if($new > $existing) + { + //change the date 2004-12-25 12:26 + $result['meta']['PO-Revision-Date'] = @date('Y-m-d H:i:s'); + + $po->fromArray($result); + $mo = $po->toMO(); + if($po->save() && $mo->save($MOFile)) + { + if(!empty($this->cache)) + $this->cache->clean($variant, $this->culture); + return true; + } + else + return false; + } + return false; + } + + /** + * Delete a particular message from the specified catalogue. + * @param string the source message to delete. + * @param string the catalogue to delete from. + * @return boolean true if deleted, false otherwise. + */ + function delete($message, $catalogue='messages') + { + $variants = $this->getVariants($catalogue); + if($variants) + list($variant, $MOFile, $POFile) = $variants; + else + return false; + + if(is_writable($MOFile) == false) + throw new TIOException("Unable to modify file {$MOFile}, file must be writable."); + if(is_writable($POFile) == false) + throw new TIOException("Unable to modify file {$POFile}, file must be writable."); + + $po = TGettext::factory('PO',$POFile); + $po->load(); + $result = $po->toArray(); + + foreach($result['strings'] as $string => $value) + { + if($string == $message) + { + $result['meta']['PO-Revision-Date'] = @date('Y-m-d H:i:s'); + unset($result['strings'][$string]); + + $po->fromArray($result); + $mo = $po->toMO(); + if($po->save() && $mo->save($MOFile)) + { + if(!empty($this->cache)) + $this->cache->clean($variant, $this->culture); + return true; + } + else + return false; + } + } + + return false; + } + + /** + * Update the translation. + * @param string the source string. + * @param string the new translation string. + * @param string comments + * @param string the catalogue of the translation. + * @return boolean true if translation was updated, false otherwise. + */ + function update($text, $target, $comments, $catalogue='messages') + { + $variants = $this->getVariants($catalogue); + if($variants) + list($variant, $MOFile, $POFile) = $variants; + else + return false; + + if(is_writable($MOFile) == false) + throw new TIOException("Unable to update file {$MOFile}, file must be writable."); + if(is_writable($POFile) == false) + throw new TIOException("Unable to update file {$POFile}, file must be writable."); + + + $po = TGettext::factory('PO',$POFile); + $po->load(); + $result = $po->toArray(); + + foreach($result['strings'] as $string => $value) + { + if($string == $text) + { + $result['strings'][$string] = $target; + $result['meta']['PO-Revision-Date'] = @date('Y-m-d H:i:s'); + + $po->fromArray($result); + $mo = $po->toMO(); + + if($po->save() && $mo->save($MOFile)) + { + if(!empty($this->cache)) + $this->cache->clean($variant, $this->culture); + return true; + } + else + return false; + } + } + + return false; + } + + + /** + * Returns a list of catalogue as key and all it variants as value. + * @return array list of catalogues + */ + function catalogues() + { + return $this->getCatalogues(); + } + + /** + * Returns a list of catalogue and its culture ID. This takes care + * of directory structures. + * E.g. array('messages','en_AU') + * @return array list of catalogues + */ + protected function getCatalogues($dir=null,$variant=null) + { + $dir = $dir?$dir:$this->source; + $files = scandir($dir); + + $catalogue = array(); + + foreach($files as $file) + { + if(is_dir($dir.'/'.$file) + && preg_match('/^[a-z]{2}(_[A-Z]{2,3})?$/',$file)) + { + + $catalogue = array_merge($catalogue, + $this->getCatalogues($dir.'/'.$file, $file)); + } + + $pos = strpos($file,$this->dataExt); + + if($pos >0 + && substr($file,-1*strlen($this->dataExt)) == $this->dataExt) + { + $name = substr($file,0,$pos); + $dot = strrpos($name,$this->dataSeparator); + $culture = $variant; + $cat = $name; + if(is_int($dot)) + { + $culture = substr($name, $dot+1,strlen($name)); + $cat = substr($name,0,$dot); + } + $details[0] = $cat; + $details[1] = $culture; + + + $catalogue[] = $details; + } + } + sort($catalogue); + + return $catalogue; + } + + protected function createMessageTemplate($catalogue) + { + if($catalogue === null) { + $catalogue = 'messages'; + } + $variants = $this->getCatalogueList($catalogue); + $variant = array_shift($variants); + $mo_file = $this->getSource($variant); + $po_file = $this->getPOFile($mo_file); + + $dir = dirname($mo_file); + if(!is_dir($dir)) + { + @mkdir($dir); + @chmod($dir,PRADO_CHMOD); + } + if(!is_dir($dir)) + throw new TException("Unable to create directory $dir"); + + $po = TGettext::factory('PO',$po_file); + $result['meta']['PO-Revision-Date'] = @date('Y-m-d H:i:s'); + $result['strings'] = array(); + + $po->fromArray($result); + $mo = $po->toMO(); + if($po->save() && $mo->save($mo_file)) + return array($variant, $mo_file, $po_file); + else + throw new TException("Unable to create file $po_file and $mo_file"); + } +} + +?> diff --git a/framework/I18N/core/NumberFormat.php b/framework/I18N/core/NumberFormat.php index 3b683c6c..3c733713 100644 --- a/framework/I18N/core/NumberFormat.php +++ b/framework/I18N/core/NumberFormat.php @@ -1,306 +1,306 @@ - - * @version $Revision: 1.6 $ $Date: 2005/12/20 09:32:42 $ - * @package System.I18N.core - */ - -/** - * Get the NumberFormatInfo class file. - */ -require_once(dirname(__FILE__).'/NumberFormatInfo.php'); - - -/** - * Get the encoding utilities - */ -require_once(dirname(__FILE__).'/util.php'); - - -/** - * NumberFormat class. - * - * NumberFormat formats decimal numbers in any locale. The decimal - * number is formatted according to a particular pattern. These - * patterns can arise from the NumberFormatInfo object which is - * culturally sensitive. The NumberFormat class can be instantiated in - * many ways. E.g. - * - * - * //create a invariant number formatter. - * $formatter = new NumberFormat(); - * - * //create a number format for the french language locale. - * $fr = new NumberFormat('fr'); - * - * //create a number format base on a NumberFormatInfo instance $numberInfo. - * $format = new NumberFormat($numberInfo); - * - * - * A normal decimal number can also be displayed as a currency - * or as a percentage. For example - * - * $format->format(1234.5); //Decimal number "1234.5" - * $format->format(1234.5,'c'); //Default currency "$1234.50" - * $format->format(0.25, 'p') //Percent "25%" - * - * - * Currency is formated using the localized currency pattern. For example - * to format the number as Japanese Yen: - * - * $ja = new NumberFormat('ja_JP'); - * - * //Japanese currency pattern, and using Japanese Yen symbol - * $ja->format(123.14,'c','JPY'); //�?123 (Yen 123) - * - * For each culture, the symbol for each currency may be different. - * - * @author Xiang Wei Zhuo - * @version v1.0, last update on Fri Dec 10 18:10:20 EST 2004 - * @package System.I18N.core - */ -class NumberFormat -{ - - /** - * The DateTimeFormatInfo, containing culture specific patterns and names. - * @var DateTimeFormatInfo - */ - protected $formatInfo; - - /** - * Create a new number format instance. The constructor can be instantiated - * with a string that represent a culture/locale. Similarly, passing - * a CultureInfo or NumberFormatInfo instance will instantiated a instance - * for that particular culture. - * @param mixed either null, a CultureInfo, a NumberFormatInfo, or string - * @return NumberFormat - */ - function __construct($formatInfo=null) - { - if($formatInfo === null) - $this->formatInfo = NumberFormatInfo::getInvariantInfo(); - else if($formatInfo instanceof CultureInfo) - $this->formatInfo = $formatInfo->NumberFormat; - else if($formatInfo instanceof NumberFormatInfo) - $this->formatInfo = $formatInfo; - else - $this->formatInfo = - NumberFormatInfo::getInstance($formatInfo); - } - - /** - * For the number for a certain pattern. The valid patterns are - * 'c', 'd', 'e', 'p' or a custom pattern, such as "#.000" for - * 3 decimal places. - * @param mixed the number to format. - * @param string the format pattern, either, 'c', 'd', 'e', 'p' - * or a custom pattern. E.g. "#.000" will format the number to - * 3 decimal places. - * @param string 3-letter ISO 4217 code. For example, the code - * "USD" represents the US Dollar and "EUR" represents the Euro currency. - * @return string formatted number string - */ - function format($number, $pattern='d', $currency='USD', $charset='UTF-8') - { - $this->setPattern($pattern); - - if(strtolower($pattern) == 'p') - $number = $number * 100; - - $string = (string)$number; - - $decimal = $this->formatDecimal($string); - $integer = $this->formatInteger(abs($number)); - - if(strlen($decimal)>0) - $result = $integer.$decimal; - else - $result = $integer; - - //get the suffix - if($number >= 0) - $suffix = $this->formatInfo->PositivePattern; - else if($number < 0) - $suffix = $this->formatInfo->NegativePattern; - else - $suffix = array("",""); - - //append and prepend suffix - $result = $suffix[0].$result.$suffix[1]; - - //replace currency sign - $symbol = @$this->formatInfo->getCurrencySymbol($currency); - if($symbol === null) { - $symbol = $currency; - } - - $result = str_replace('¤',$symbol, $result); - - return I18N_toEncoding($result, $charset); - } - - /** - * For the integer, perform groupings and string padding. - * @param string the decimal number in string form. - * @return string formatted integer string with grouping - */ - protected function formatInteger($string) - { - $string = (string)$string; - - $decimalDigits = $this->formatInfo->DecimalDigits; - //if not decimal digits, assume 0 decimal points. - if(is_int($decimalDigits) && $decimalDigits > 0) - $string = (string)round(floatval($string),$decimalDigits); - $dp = strpos($string, '.'); - if(is_int($dp)) - $string = substr($string, 0, $dp); - $integer = ''; - - $digitSize = $this->formatInfo->getDigitSize(); - - $string = str_pad($string, $digitSize, '0',STR_PAD_LEFT); - - $len = strlen($string); - - $groupSeparator = $this->formatInfo->GroupSeparator; - $groupSize = $this->formatInfo->GroupSizes; - - - $firstGroup = true; - $multiGroup = is_int($groupSize[1]); - $count = 0; - - if(is_int($groupSize[0])) - { - //now for the integer groupings - for($i=0; $i<$len; $i++) - { - $char = $string{$len-$i-1}; - - if($multiGroup && $count == 0) - { - if($i != 0 && $i%$groupSize[0] == 0) - { - $integer = $groupSeparator . $integer; - $count++; - } - } - else if($multiGroup && $count >= 1) - { - if($i != 0 && ($i-$groupSize[0])%$groupSize[1] == 0) - { - $integer = $groupSeparator . $integer; - $count++; - } - } - else - { - if($i != 0 && $i%$groupSize[0] == 0) - { - $integer = $groupSeparator . $integer; - $count++; - } - } - - $integer = $char . $integer; - } - } - else - $integer = $string; - - return $integer; - } - - /** - * Format the decimal places. - * @param string the decimal number in string form. - * @return string formatted decimal places. - */ - protected function formatDecimal($string) - { - $dp = strpos($string, '.'); - $decimal = ''; - - $decimalDigits = $this->formatInfo->DecimalDigits; - $decimalSeparator = $this->formatInfo->DecimalSeparator; - - //do the correct rounding here - //$string = round(floatval($string), $decimalDigits); - if(is_int($dp)) - { - if($decimalDigits == -1) - { - $decimal = substr($string, $dp+1); - } - else if(is_int($decimalDigits)) - { - $float = round((float)$string, $decimalDigits); - if(strpos((string)$float, '.') === false) - { - $decimal = str_pad($decimal,$decimalDigits,'0'); - } - else - { - $decimal = substr($float, strpos($float,'.')+1); - if(strlen($decimal)<$decimalDigits) - $decimal = str_pad($decimal,$decimalDigits,'0'); - } - } - else - return $decimal; - - return $decimalSeparator.$decimal; - } - else if ($decimalDigits > 0) - return $decimalSeparator.str_pad($decimal,$decimalDigits,'0'); - - return $decimal; - } - - /** - * Set the pattern to format against. The default patterns - * are retrieved from the NumberFormatInfo instance. - * @param string the requested patterns. - * @return string a number format pattern. - */ - protected function setPattern($pattern) - { - switch($pattern) - { - case 'c': - case 'C': - $this->formatInfo->setPattern(NumberFormatInfo::CURRENCY); - break; - case 'd': - case 'D': - $this->formatInfo->setPattern(NumberFormatInfo::DECIMAL); - break; - case 'e': - case 'E': - $this->formatInfo->setPattern(NumberFormatInfo::SCIENTIFIC); - break; - case 'p': - case 'P': - $this->formatInfo->setPattern(NumberFormatInfo::PERCENTAGE); - break; - default: - $this->formatInfo->setPattern($pattern); - break; - } - } -} - + + * @version $Revision: 1.6 $ $Date: 2005/12/20 09:32:42 $ + * @package System.I18N.core + */ + +/** + * Get the NumberFormatInfo class file. + */ +require_once(dirname(__FILE__).'/NumberFormatInfo.php'); + + +/** + * Get the encoding utilities + */ +require_once(dirname(__FILE__).'/util.php'); + + +/** + * NumberFormat class. + * + * NumberFormat formats decimal numbers in any locale. The decimal + * number is formatted according to a particular pattern. These + * patterns can arise from the NumberFormatInfo object which is + * culturally sensitive. The NumberFormat class can be instantiated in + * many ways. E.g. + * + * + * //create a invariant number formatter. + * $formatter = new NumberFormat(); + * + * //create a number format for the french language locale. + * $fr = new NumberFormat('fr'); + * + * //create a number format base on a NumberFormatInfo instance $numberInfo. + * $format = new NumberFormat($numberInfo); + * + * + * A normal decimal number can also be displayed as a currency + * or as a percentage. For example + * + * $format->format(1234.5); //Decimal number "1234.5" + * $format->format(1234.5,'c'); //Default currency "$1234.50" + * $format->format(0.25, 'p') //Percent "25%" + * + * + * Currency is formated using the localized currency pattern. For example + * to format the number as Japanese Yen: + * + * $ja = new NumberFormat('ja_JP'); + * + * //Japanese currency pattern, and using Japanese Yen symbol + * $ja->format(123.14,'c','JPY'); //�?123 (Yen 123) + * + * For each culture, the symbol for each currency may be different. + * + * @author Xiang Wei Zhuo + * @version v1.0, last update on Fri Dec 10 18:10:20 EST 2004 + * @package System.I18N.core + */ +class NumberFormat +{ + + /** + * The DateTimeFormatInfo, containing culture specific patterns and names. + * @var DateTimeFormatInfo + */ + protected $formatInfo; + + /** + * Create a new number format instance. The constructor can be instantiated + * with a string that represent a culture/locale. Similarly, passing + * a CultureInfo or NumberFormatInfo instance will instantiated a instance + * for that particular culture. + * @param mixed either null, a CultureInfo, a NumberFormatInfo, or string + * @return NumberFormat + */ + function __construct($formatInfo=null) + { + if($formatInfo === null) + $this->formatInfo = NumberFormatInfo::getInvariantInfo(); + else if($formatInfo instanceof CultureInfo) + $this->formatInfo = $formatInfo->NumberFormat; + else if($formatInfo instanceof NumberFormatInfo) + $this->formatInfo = $formatInfo; + else + $this->formatInfo = + NumberFormatInfo::getInstance($formatInfo); + } + + /** + * For the number for a certain pattern. The valid patterns are + * 'c', 'd', 'e', 'p' or a custom pattern, such as "#.000" for + * 3 decimal places. + * @param mixed the number to format. + * @param string the format pattern, either, 'c', 'd', 'e', 'p' + * or a custom pattern. E.g. "#.000" will format the number to + * 3 decimal places. + * @param string 3-letter ISO 4217 code. For example, the code + * "USD" represents the US Dollar and "EUR" represents the Euro currency. + * @return string formatted number string + */ + function format($number, $pattern='d', $currency='USD', $charset='UTF-8') + { + $this->setPattern($pattern); + + if(strtolower($pattern) == 'p') + $number = $number * 100; + + $string = (string)$number; + + $decimal = $this->formatDecimal($string); + $integer = $this->formatInteger(abs($number)); + + if(strlen($decimal)>0) + $result = $integer.$decimal; + else + $result = $integer; + + //get the suffix + if($number >= 0) + $suffix = $this->formatInfo->PositivePattern; + else if($number < 0) + $suffix = $this->formatInfo->NegativePattern; + else + $suffix = array("",""); + + //append and prepend suffix + $result = $suffix[0].$result.$suffix[1]; + + //replace currency sign + $symbol = @$this->formatInfo->getCurrencySymbol($currency); + if($symbol === null) { + $symbol = $currency; + } + + $result = str_replace('¤',$symbol, $result); + + return I18N_toEncoding($result, $charset); + } + + /** + * For the integer, perform groupings and string padding. + * @param string the decimal number in string form. + * @return string formatted integer string with grouping + */ + protected function formatInteger($string) + { + $string = (string)$string; + + $decimalDigits = $this->formatInfo->DecimalDigits; + //if not decimal digits, assume 0 decimal points. + if(is_int($decimalDigits) && $decimalDigits > 0) + $string = (string)round(floatval($string),$decimalDigits); + $dp = strpos($string, '.'); + if(is_int($dp)) + $string = substr($string, 0, $dp); + $integer = ''; + + $digitSize = $this->formatInfo->getDigitSize(); + + $string = str_pad($string, $digitSize, '0',STR_PAD_LEFT); + + $len = strlen($string); + + $groupSeparator = $this->formatInfo->GroupSeparator; + $groupSize = $this->formatInfo->GroupSizes; + + + $firstGroup = true; + $multiGroup = is_int($groupSize[1]); + $count = 0; + + if(is_int($groupSize[0])) + { + //now for the integer groupings + for($i=0; $i<$len; $i++) + { + $char = $string{$len-$i-1}; + + if($multiGroup && $count == 0) + { + if($i != 0 && $i%$groupSize[0] == 0) + { + $integer = $groupSeparator . $integer; + $count++; + } + } + else if($multiGroup && $count >= 1) + { + if($i != 0 && ($i-$groupSize[0])%$groupSize[1] == 0) + { + $integer = $groupSeparator . $integer; + $count++; + } + } + else + { + if($i != 0 && $i%$groupSize[0] == 0) + { + $integer = $groupSeparator . $integer; + $count++; + } + } + + $integer = $char . $integer; + } + } + else + $integer = $string; + + return $integer; + } + + /** + * Format the decimal places. + * @param string the decimal number in string form. + * @return string formatted decimal places. + */ + protected function formatDecimal($string) + { + $dp = strpos($string, '.'); + $decimal = ''; + + $decimalDigits = $this->formatInfo->DecimalDigits; + $decimalSeparator = $this->formatInfo->DecimalSeparator; + + //do the correct rounding here + //$string = round(floatval($string), $decimalDigits); + if(is_int($dp)) + { + if($decimalDigits == -1) + { + $decimal = substr($string, $dp+1); + } + else if(is_int($decimalDigits)) + { + $float = round((float)$string, $decimalDigits); + if(strpos((string)$float, '.') === false) + { + $decimal = str_pad($decimal,$decimalDigits,'0'); + } + else + { + $decimal = substr($float, strpos($float,'.')+1); + if(strlen($decimal)<$decimalDigits) + $decimal = str_pad($decimal,$decimalDigits,'0'); + } + } + else + return $decimal; + + return $decimalSeparator.$decimal; + } + else if ($decimalDigits > 0) + return $decimalSeparator.str_pad($decimal,$decimalDigits,'0'); + + return $decimal; + } + + /** + * Set the pattern to format against. The default patterns + * are retrieved from the NumberFormatInfo instance. + * @param string the requested patterns. + * @return string a number format pattern. + */ + protected function setPattern($pattern) + { + switch($pattern) + { + case 'c': + case 'C': + $this->formatInfo->setPattern(NumberFormatInfo::CURRENCY); + break; + case 'd': + case 'D': + $this->formatInfo->setPattern(NumberFormatInfo::DECIMAL); + break; + case 'e': + case 'E': + $this->formatInfo->setPattern(NumberFormatInfo::SCIENTIFIC); + break; + case 'p': + case 'P': + $this->formatInfo->setPattern(NumberFormatInfo::PERCENTAGE); + break; + default: + $this->formatInfo->setPattern($pattern); + break; + } + } +} + diff --git a/framework/I18N/core/NumberFormatInfo.php b/framework/I18N/core/NumberFormatInfo.php index 17149317..2a666726 100644 --- a/framework/I18N/core/NumberFormatInfo.php +++ b/framework/I18N/core/NumberFormatInfo.php @@ -1,650 +1,650 @@ - - * @version $Revision: 1.3 $ $Date: 2005/08/04 05:27:19 $ - * @package System.I18N.core - */ - -/** - * Get the CultureInfo class file. - */ -require_once(dirname(__FILE__).'/CultureInfo.php'); - -/** - * NumberFormatInfo class - * - * Defines how numeric values are formatted and displayed, - * depending on the culture. Numeric values are formatted using - * standard or custom patterns stored in the properties of a - * NumberFormatInfo. - * - * This class contains information, such as currency, decimal - * separators, and other numeric symbols. - * - * To create a NumberFormatInfo for a specific culture, - * create a CultureInfo for that culture and retrieve the - * CultureInfo->NumberFormat property. Or use - * NumberFormatInfo::getInstance($culture). - * To create a NumberFormatInfo for the invariant culture, use the - * InvariantInfo::getInvariantInfo(). - * - * - * @author Xiang Wei Zhuo - * @version v1.0, last update on Sun Dec 05 14:48:26 EST 2004 - * @package System.I18N.core - */ -class NumberFormatInfo -{ - - /** - * ICU number formatting data. - * @var array - */ - private $data = array(); - - /** - * A list of properties that are accessable/writable. - * @var array - */ - protected $properties = array(); - - /** - * The number pattern. - * @var array - */ - protected $pattern = array(); - - const DECIMAL = 0; - const CURRENCY = 1; - const PERCENTAGE = 2; - const SCIENTIFIC = 3; - - /** - * Allow functions that begins with 'set' to be called directly - * as an attribute/property to retrieve the value. - * @return mixed - */ - public function __get($name) - { - $getProperty = 'get'.$name; - if(in_array($getProperty, $this->properties)) - return $this->$getProperty(); - else - throw new Exception('Property '.$name.' does not exists.'); - } - - /** - * Allow functions that begins with 'set' to be called directly - * as an attribute/property to set the value. - */ - public function __set($name, $value) - { - $setProperty = 'set'.$name; - if(in_array($setProperty, $this->properties)) - $this->$setProperty($value); - else - throw new Exception('Property '.$name.' can not be set.'); - } - - /** - * Initializes a new writable instance of the NumberFormatInfo class - * that is dependent on the ICU data for number, decimal, and currency - * formatting information. N.B.You should not initialize this - * class directly unless you know what you are doing. Please use use - * NumberFormatInfo::getInstance() to create an instance. - * @param array ICU data for date time formatting. - * @see getInstance() - */ - public function __construct($data=array(), $type=NumberFormatInfo::DECIMAL) - { - $this->properties = get_class_methods($this); - - if(empty($data)) - throw new Exception('Please provide the ICU data to initialize.'); - - $this->data = $data; - - $this->setPattern($type); - } - - /** - * Set the pattern for a specific number pattern. The validate patterns - * NumberFormatInfo::DECIMAL, NumberFormatInfo::CURRENCY, - * NumberFormatInfo::PERCENTAGE, or NumberFormatInfo::SCIENTIFIC - * @param int pattern type. - */ - public function setPattern($type=NumberFormatInfo::DECIMAL) - { - if(is_int($type)) - $this->pattern = - $this->parsePattern($this->data['NumberPatterns'][$type]); - else - $this->pattern = $this->parsePattern($type); - - $this->pattern['negInfty'] = - $this->data['NumberElements'][6]. - $this->data['NumberElements'][9]; - - $this->pattern['posInfty'] = - $this->data['NumberElements'][11]. - $this->data['NumberElements'][9]; - } - - public function getPattern() - { - return $this->pattern; - } - - /** - * Gets the default NumberFormatInfo that is culture-independent - * (invariant). - * @return NumberFormatInfo default NumberFormatInfo. - */ - public static function getInvariantInfo($type=NumberFormatInfo::DECIMAL) - { - static $invariant; - if($invariant === null) - { - $culture = CultureInfo::getInvariantCulture(); - $invariant = $culture->NumberFormat; - $invariant->setPattern($type); - } - return $invariant; - } - - /** - * Returns the NumberFormatInfo associated with the specified culture. - * @param CultureInfo the culture that gets the NumberFormat property. - * @param int the number formatting type, it should be - * NumberFormatInfo::DECIMAL, NumberFormatInfo::CURRENCY, - * NumberFormatInfo::PERCENTAGE, or NumberFormatInfo::SCIENTIFIC - * @return NumberFormatInfo NumberFormatInfo for the specified - * culture. - * @see getCurrencyInstance(); - * @see getPercentageInstance(); - * @see getScientificInstance(); - */ - public static function getInstance($culture=null, - $type=NumberFormatInfo::DECIMAL) - { - if ($culture instanceof CultureInfo) - { - $formatInfo = $culture->NumberFormat; - $formatInfo->setPattern($type); - return $formatInfo; - } - else if(is_string($culture)) - { - $cultureInfo = new CultureInfo($culture); - $formatInfo = $cultureInfo->NumberFormat; - $formatInfo->setPattern($type); - return $formatInfo; - } - else - { - $cultureInfo = new CultureInfo(); - $formatInfo = $cultureInfo->NumberFormat; - $formatInfo->setPattern($type); - return $formatInfo; - } - } - - /** - * Returns the currency format info associated with the specified culture. - * @param CultureInfo the culture that gets the NumberFormat property. - * @return NumberFormatInfo NumberFormatInfo for the specified - * culture. - */ - public static function getCurrencyInstance($culture=null) - { - return self::getInstance($culture, self::CURRENCY); - } - - /** - * Returns the percentage format info associated with the specified culture. - * @param CultureInfo the culture that gets the NumberFormat property. - * @return NumberFormatInfo NumberFormatInfo for the specified - * culture. - */ - public static function getPercentageInstance($culture=null) - { - return self::getInstance($culture, self::PERCENTAGE); - } - - /** - * Returns the scientific format info associated with the specified culture. - * @param CultureInfo the culture that gets the NumberFormat property. - * @return NumberFormatInfo NumberFormatInfo for the specified - * culture. - */ - public static function getScientificInstance($culture=null) - { - return self::getInstance($culture, self::SCIENTIFIC); - } - - /** - * Parse the given pattern and return a list of known properties. - * @param string a number pattern. - * @return array list of pattern properties. - */ - protected function parsePattern($pattern) - { - $pattern = explode(';',$pattern); - - $negative = null; - if(count($pattern) > 1) - $negative = $pattern[1]; - $pattern = $pattern[0]; - - $comma = ','; - $dot = '.'; - $digit = '0'; - $hash = '#'; - - //find the first group point, and decimal point - $groupPos1 = strrpos($pattern,$comma); - $decimalPos = strrpos($pattern,$dot); - - $groupPos2 = false; - $groupSize1 = false; - $groupSize2 = false; - $decimalPoints = is_int($decimalPos)?-1:false; - - $info['negPref'] = $this->data['NumberElements'][6]; - $info['negPost'] = ''; - - $info['negative'] = $negative; - $info['positive'] = $pattern; - - //find the negative prefix and postfix - if($negative) - { - $prefixPostfix = $this->getPrePostfix($negative); - $info['negPref'] = $prefixPostfix[0]; - $info['negPost'] = $prefixPostfix[1]; - } - - $posfix = $this->getPrePostfix($pattern); - $info['posPref'] = $posfix[0]; - $info['posPost'] = $posfix[1]; - - //var_dump($pattern); - //var_dump($decimalPos); - if(is_int($groupPos1)) - { - //get the second group - $groupPos2 = strrpos(substr($pattern,0,$groupPos1),$comma); - - //get the number of decimal digits - if(is_int($decimalPos)) - { - $groupSize1 = $decimalPos - $groupPos1-1; - - } - else - { - //no decimal point, so traverse from the back - //to find the groupsize 1. - for($i=strlen($pattern)-1; $i>=0; $i--) - { - if($pattern{$i} == $digit || $pattern{$i}==$hash) - { - $groupSize1 = $i - $groupPos1; - break; - } - } - } - - //get the second group size - if(is_int($groupPos2)) - $groupSize2 = $groupPos1 - $groupPos2-1; - } - - if(is_int($decimalPos)) - { - for($i=strlen($pattern)-1; $i>=0; $i--) - { - if($pattern{$i} == $dot) break; - if($pattern{$i} == $digit) - { - $decimalPoints = $i - $decimalPos; - break; - } - } - } - - if(is_int($decimalPos)) - $digitPattern = substr($pattern,0,$decimalPos); - else - $digitPattern = $pattern; - - $digitPattern = preg_replace('/[^0]/','',$digitPattern); - - $info['groupPos1'] = $groupPos1; - $info['groupSize1'] = $groupSize1; - $info['groupPos2'] = $groupPos2; - $info['groupSize2'] = $groupSize2; - $info['decimalPos'] = $decimalPos; - $info['decimalPoints'] = $decimalPoints; - $info['digitSize'] = strlen($digitPattern); - return $info; - } - - /** - * Get the prefix and postfix of a pattern. - * @param string pattern - * @return array of prefix and postfix, array(prefix,postfix). - */ - protected function getPrePostfix($pattern) - { - $regexp = '/[#,\.0]+/'; - $result = preg_split($regexp, $pattern); - return array($result[0],$result[1]); - } - - - /** - * Indicates the number of decimal places. - * @return int number of decimal places. - */ - function getDecimalDigits() - { - return $this->pattern['decimalPoints']; - } - - /** - * Set the number of decimal places. - * @param int number of decimal places. - */ - function setDecimalDigits($value) - { - return $this->pattern['decimalPoints'] = $value; - } - - function getDigitSize() - { - return $this->pattern['digitSize']; - } - - function setDigitSize($value) - { - $this->pattern['digitSize'] = $value; - } - - /** - * Gets the string to use as the decimal separator. - * @return string decimal separator. - */ - function getDecimalSeparator() - { - return $this->data['NumberElements'][0]; - } - - /** - * Set the string to use as the decimal separator. - * @param string the decimal point - */ - function setDecimalSeparator($value) - { - return $this->data['NumberElements'][0] = $value; - } - - /** - * Gets the string that separates groups of digits to the left - * of the decimal in currency values. - * @param parameter - * @return string currency group separator. - */ - function getGroupSeparator() - { - return $this->data['NumberElements'][1]; - } - - /** - * Set the string to use as the group separator. - * @param string the group separator. - */ - function setGroupSeparator($value) - { - return $this->data['NumberElements'][1] = $value; - } - - /** - * Gets the number of digits in each group to the left of the decimal - * There can be two grouping sizes, this fucntion - * returns array(group1, group2), if there is only 1 grouping size, - * group2 will be false. - * @return array grouping size(s). - */ - function getGroupSizes() - { - $group1 = $this->pattern['groupSize1']; - $group2 = $this->pattern['groupSize2']; - - return array($group1, $group2); - } - - /** - * Set the number of digits in each group to the left of the decimal. - * There can be two grouping sizes, the value should - * be an array(group1, group2), if there is only 1 grouping size, - * group2 should be false. - * @param array grouping size(s). - */ - function setGroupSizes($groupSize) - { - $this->pattern['groupSize1'] = $groupSize[0]; - $this->pattern['groupSize2'] = $groupSize[1]; - } - - /** - * Gets the format pattern for negative values. - * The negative pattern is composed of a prefix, and postfix. - * This function returns array(prefix, postfix). - * @return arary negative pattern. - */ - function getNegativePattern() - { - $prefix = $this->pattern['negPref']; - $postfix = $this->pattern['negPost']; - return array($prefix, $postfix); - } - - /** - * Set the format pattern for negative values. - * The negative pattern is composed of a prefix, and postfix in the form - * array(prefix, postfix). - * @param arary negative pattern. - */ - function setNegativePattern($pattern) - { - $this->pattern['negPref'] = $pattern[0]; - $this->pattern['negPost'] = $pattern[1]; - } - - /** - * Gets the format pattern for positive values. - * The positive pattern is composed of a prefix, and postfix. - * This function returns array(prefix, postfix). - * @return arary positive pattern. - */ - function getPositivePattern() - { - $prefix = $this->pattern['posPref']; - $postfix = $this->pattern['posPost']; - return array($prefix, $postfix); - } - - /** - * Set the format pattern for positive values. - * The positive pattern is composed of a prefix, and postfix in the form - * array(prefix, postfix). - * @param arary positive pattern. - */ - function setPositivePattern($pattern) - { - $this->pattern['posPref'] = $pattern[0]; - $this->pattern['posPost'] = $pattern[1]; - } - - /** - * Gets the string to use as the currency symbol. - * @return string currency symbol. - */ - function getCurrencySymbol($currency='USD') - { - if(isset($this->pattern['symbol'])) - return $this->pattern['symbol']; - else - return $this->data['Currencies'][$currency][0]; - } - - - /** - * Set the string to use as the currency symbol. - * @param string currency symbol. - */ - function setCurrencySymbol($symbol) - { - $this->pattern['symbol'] = $symbol; - } - - /** - * Gets the string that represents negative infinity. - * @return string negative infinity. - */ - function getNegativeInfinitySymbol() - { - return $this->pattern['negInfty']; - } - - /** - * Set the string that represents negative infinity. - * @param string negative infinity. - */ - function setNegativeInfinitySymbol($value) - { - $this->pattern['negInfty'] = $value; - } - - /** - * Gets the string that represents positive infinity. - * @return string positive infinity. - */ - function getPositiveInfinitySymbol() - { - return $this->pattern['posInfty']; - } - - /** - * Set the string that represents positive infinity. - * @param string positive infinity. - */ - function setPositiveInfinitySymbol($value) - { - $this->pattern['posInfty'] = $value; - } - - /** - * Gets the string that denotes that the associated number is negative. - * @return string negative sign. - */ - function getNegativeSign() - { - return $this->data['NumberElements'][6]; - } - - /** - * Set the string that denotes that the associated number is negative. - * @param string negative sign. - */ - function setNegativeSign($value) - { - $this->data['NumberElements'][6] = $value; - } - - /** - * Gets the string that denotes that the associated number is positive. - * @return string positive sign. - */ - function getPositiveSign() - { - return $this->data['NumberElements'][11]; - } - - /** - * Set the string that denotes that the associated number is positive. - * @param string positive sign. - */ - function setPositiveSign($value) - { - $this->data['NumberElements'][11] = $value; - } - - /** - * Gets the string that represents the IEEE NaN (not a number) value. - * @return string NaN symbol. - */ - function getNaNSymbol() - { - return $this->data['NumberElements'][10]; - } - - /** - * Set the string that represents the IEEE NaN (not a number) value. - * @param string NaN symbol. - */ - function setNaNSymbol($value) - { - $this->data['NumberElements'][10] = $value; - } - - /** - * Gets the string to use as the percent symbol. - * @return string percent symbol. - */ - function getPercentSymbol() - { - return $this->data['NumberElements'][3]; - } - - /** - * Set the string to use as the percent symbol. - * @param string percent symbol. - */ - function setPercentSymbol($value) - { - $this->data['NumberElements'][3] = $value; - } - - /** - * Gets the string to use as the per mille symbol. - * @return string percent symbol. - */ - function getPerMilleSymbol() - { - return $this->data['NumberElements'][8]; - } - - /** - * Set the string to use as the per mille symbol. - * @param string percent symbol. - */ - function setPerMilleSymbol($value) - { - $this->data['NumberElements'][8] = $value; - } -} - + + * @version $Revision: 1.3 $ $Date: 2005/08/04 05:27:19 $ + * @package System.I18N.core + */ + +/** + * Get the CultureInfo class file. + */ +require_once(dirname(__FILE__).'/CultureInfo.php'); + +/** + * NumberFormatInfo class + * + * Defines how numeric values are formatted and displayed, + * depending on the culture. Numeric values are formatted using + * standard or custom patterns stored in the properties of a + * NumberFormatInfo. + * + * This class contains information, such as currency, decimal + * separators, and other numeric symbols. + * + * To create a NumberFormatInfo for a specific culture, + * create a CultureInfo for that culture and retrieve the + * CultureInfo->NumberFormat property. Or use + * NumberFormatInfo::getInstance($culture). + * To create a NumberFormatInfo for the invariant culture, use the + * InvariantInfo::getInvariantInfo(). + * + * + * @author Xiang Wei Zhuo + * @version v1.0, last update on Sun Dec 05 14:48:26 EST 2004 + * @package System.I18N.core + */ +class NumberFormatInfo +{ + + /** + * ICU number formatting data. + * @var array + */ + private $data = array(); + + /** + * A list of properties that are accessable/writable. + * @var array + */ + protected $properties = array(); + + /** + * The number pattern. + * @var array + */ + protected $pattern = array(); + + const DECIMAL = 0; + const CURRENCY = 1; + const PERCENTAGE = 2; + const SCIENTIFIC = 3; + + /** + * Allow functions that begins with 'set' to be called directly + * as an attribute/property to retrieve the value. + * @return mixed + */ + public function __get($name) + { + $getProperty = 'get'.$name; + if(in_array($getProperty, $this->properties)) + return $this->$getProperty(); + else + throw new Exception('Property '.$name.' does not exists.'); + } + + /** + * Allow functions that begins with 'set' to be called directly + * as an attribute/property to set the value. + */ + public function __set($name, $value) + { + $setProperty = 'set'.$name; + if(in_array($setProperty, $this->properties)) + $this->$setProperty($value); + else + throw new Exception('Property '.$name.' can not be set.'); + } + + /** + * Initializes a new writable instance of the NumberFormatInfo class + * that is dependent on the ICU data for number, decimal, and currency + * formatting information. N.B.You should not initialize this + * class directly unless you know what you are doing. Please use use + * NumberFormatInfo::getInstance() to create an instance. + * @param array ICU data for date time formatting. + * @see getInstance() + */ + public function __construct($data=array(), $type=NumberFormatInfo::DECIMAL) + { + $this->properties = get_class_methods($this); + + if(empty($data)) + throw new Exception('Please provide the ICU data to initialize.'); + + $this->data = $data; + + $this->setPattern($type); + } + + /** + * Set the pattern for a specific number pattern. The validate patterns + * NumberFormatInfo::DECIMAL, NumberFormatInfo::CURRENCY, + * NumberFormatInfo::PERCENTAGE, or NumberFormatInfo::SCIENTIFIC + * @param int pattern type. + */ + public function setPattern($type=NumberFormatInfo::DECIMAL) + { + if(is_int($type)) + $this->pattern = + $this->parsePattern($this->data['NumberPatterns'][$type]); + else + $this->pattern = $this->parsePattern($type); + + $this->pattern['negInfty'] = + $this->data['NumberElements'][6]. + $this->data['NumberElements'][9]; + + $this->pattern['posInfty'] = + $this->data['NumberElements'][11]. + $this->data['NumberElements'][9]; + } + + public function getPattern() + { + return $this->pattern; + } + + /** + * Gets the default NumberFormatInfo that is culture-independent + * (invariant). + * @return NumberFormatInfo default NumberFormatInfo. + */ + public static function getInvariantInfo($type=NumberFormatInfo::DECIMAL) + { + static $invariant; + if($invariant === null) + { + $culture = CultureInfo::getInvariantCulture(); + $invariant = $culture->NumberFormat; + $invariant->setPattern($type); + } + return $invariant; + } + + /** + * Returns the NumberFormatInfo associated with the specified culture. + * @param CultureInfo the culture that gets the NumberFormat property. + * @param int the number formatting type, it should be + * NumberFormatInfo::DECIMAL, NumberFormatInfo::CURRENCY, + * NumberFormatInfo::PERCENTAGE, or NumberFormatInfo::SCIENTIFIC + * @return NumberFormatInfo NumberFormatInfo for the specified + * culture. + * @see getCurrencyInstance(); + * @see getPercentageInstance(); + * @see getScientificInstance(); + */ + public static function getInstance($culture=null, + $type=NumberFormatInfo::DECIMAL) + { + if ($culture instanceof CultureInfo) + { + $formatInfo = $culture->NumberFormat; + $formatInfo->setPattern($type); + return $formatInfo; + } + else if(is_string($culture)) + { + $cultureInfo = new CultureInfo($culture); + $formatInfo = $cultureInfo->NumberFormat; + $formatInfo->setPattern($type); + return $formatInfo; + } + else + { + $cultureInfo = new CultureInfo(); + $formatInfo = $cultureInfo->NumberFormat; + $formatInfo->setPattern($type); + return $formatInfo; + } + } + + /** + * Returns the currency format info associated with the specified culture. + * @param CultureInfo the culture that gets the NumberFormat property. + * @return NumberFormatInfo NumberFormatInfo for the specified + * culture. + */ + public static function getCurrencyInstance($culture=null) + { + return self::getInstance($culture, self::CURRENCY); + } + + /** + * Returns the percentage format info associated with the specified culture. + * @param CultureInfo the culture that gets the NumberFormat property. + * @return NumberFormatInfo NumberFormatInfo for the specified + * culture. + */ + public static function getPercentageInstance($culture=null) + { + return self::getInstance($culture, self::PERCENTAGE); + } + + /** + * Returns the scientific format info associated with the specified culture. + * @param CultureInfo the culture that gets the NumberFormat property. + * @return NumberFormatInfo NumberFormatInfo for the specified + * culture. + */ + public static function getScientificInstance($culture=null) + { + return self::getInstance($culture, self::SCIENTIFIC); + } + + /** + * Parse the given pattern and return a list of known properties. + * @param string a number pattern. + * @return array list of pattern properties. + */ + protected function parsePattern($pattern) + { + $pattern = explode(';',$pattern); + + $negative = null; + if(count($pattern) > 1) + $negative = $pattern[1]; + $pattern = $pattern[0]; + + $comma = ','; + $dot = '.'; + $digit = '0'; + $hash = '#'; + + //find the first group point, and decimal point + $groupPos1 = strrpos($pattern,$comma); + $decimalPos = strrpos($pattern,$dot); + + $groupPos2 = false; + $groupSize1 = false; + $groupSize2 = false; + $decimalPoints = is_int($decimalPos)?-1:false; + + $info['negPref'] = $this->data['NumberElements'][6]; + $info['negPost'] = ''; + + $info['negative'] = $negative; + $info['positive'] = $pattern; + + //find the negative prefix and postfix + if($negative) + { + $prefixPostfix = $this->getPrePostfix($negative); + $info['negPref'] = $prefixPostfix[0]; + $info['negPost'] = $prefixPostfix[1]; + } + + $posfix = $this->getPrePostfix($pattern); + $info['posPref'] = $posfix[0]; + $info['posPost'] = $posfix[1]; + + //var_dump($pattern); + //var_dump($decimalPos); + if(is_int($groupPos1)) + { + //get the second group + $groupPos2 = strrpos(substr($pattern,0,$groupPos1),$comma); + + //get the number of decimal digits + if(is_int($decimalPos)) + { + $groupSize1 = $decimalPos - $groupPos1-1; + + } + else + { + //no decimal point, so traverse from the back + //to find the groupsize 1. + for($i=strlen($pattern)-1; $i>=0; $i--) + { + if($pattern{$i} == $digit || $pattern{$i}==$hash) + { + $groupSize1 = $i - $groupPos1; + break; + } + } + } + + //get the second group size + if(is_int($groupPos2)) + $groupSize2 = $groupPos1 - $groupPos2-1; + } + + if(is_int($decimalPos)) + { + for($i=strlen($pattern)-1; $i>=0; $i--) + { + if($pattern{$i} == $dot) break; + if($pattern{$i} == $digit) + { + $decimalPoints = $i - $decimalPos; + break; + } + } + } + + if(is_int($decimalPos)) + $digitPattern = substr($pattern,0,$decimalPos); + else + $digitPattern = $pattern; + + $digitPattern = preg_replace('/[^0]/','',$digitPattern); + + $info['groupPos1'] = $groupPos1; + $info['groupSize1'] = $groupSize1; + $info['groupPos2'] = $groupPos2; + $info['groupSize2'] = $groupSize2; + $info['decimalPos'] = $decimalPos; + $info['decimalPoints'] = $decimalPoints; + $info['digitSize'] = strlen($digitPattern); + return $info; + } + + /** + * Get the prefix and postfix of a pattern. + * @param string pattern + * @return array of prefix and postfix, array(prefix,postfix). + */ + protected function getPrePostfix($pattern) + { + $regexp = '/[#,\.0]+/'; + $result = preg_split($regexp, $pattern); + return array($result[0],$result[1]); + } + + + /** + * Indicates the number of decimal places. + * @return int number of decimal places. + */ + function getDecimalDigits() + { + return $this->pattern['decimalPoints']; + } + + /** + * Set the number of decimal places. + * @param int number of decimal places. + */ + function setDecimalDigits($value) + { + return $this->pattern['decimalPoints'] = $value; + } + + function getDigitSize() + { + return $this->pattern['digitSize']; + } + + function setDigitSize($value) + { + $this->pattern['digitSize'] = $value; + } + + /** + * Gets the string to use as the decimal separator. + * @return string decimal separator. + */ + function getDecimalSeparator() + { + return $this->data['NumberElements'][0]; + } + + /** + * Set the string to use as the decimal separator. + * @param string the decimal point + */ + function setDecimalSeparator($value) + { + return $this->data['NumberElements'][0] = $value; + } + + /** + * Gets the string that separates groups of digits to the left + * of the decimal in currency values. + * @param parameter + * @return string currency group separator. + */ + function getGroupSeparator() + { + return $this->data['NumberElements'][1]; + } + + /** + * Set the string to use as the group separator. + * @param string the group separator. + */ + function setGroupSeparator($value) + { + return $this->data['NumberElements'][1] = $value; + } + + /** + * Gets the number of digits in each group to the left of the decimal + * There can be two grouping sizes, this fucntion + * returns array(group1, group2), if there is only 1 grouping size, + * group2 will be false. + * @return array grouping size(s). + */ + function getGroupSizes() + { + $group1 = $this->pattern['groupSize1']; + $group2 = $this->pattern['groupSize2']; + + return array($group1, $group2); + } + + /** + * Set the number of digits in each group to the left of the decimal. + * There can be two grouping sizes, the value should + * be an array(group1, group2), if there is only 1 grouping size, + * group2 should be false. + * @param array grouping size(s). + */ + function setGroupSizes($groupSize) + { + $this->pattern['groupSize1'] = $groupSize[0]; + $this->pattern['groupSize2'] = $groupSize[1]; + } + + /** + * Gets the format pattern for negative values. + * The negative pattern is composed of a prefix, and postfix. + * This function returns array(prefix, postfix). + * @return arary negative pattern. + */ + function getNegativePattern() + { + $prefix = $this->pattern['negPref']; + $postfix = $this->pattern['negPost']; + return array($prefix, $postfix); + } + + /** + * Set the format pattern for negative values. + * The negative pattern is composed of a prefix, and postfix in the form + * array(prefix, postfix). + * @param arary negative pattern. + */ + function setNegativePattern($pattern) + { + $this->pattern['negPref'] = $pattern[0]; + $this->pattern['negPost'] = $pattern[1]; + } + + /** + * Gets the format pattern for positive values. + * The positive pattern is composed of a prefix, and postfix. + * This function returns array(prefix, postfix). + * @return arary positive pattern. + */ + function getPositivePattern() + { + $prefix = $this->pattern['posPref']; + $postfix = $this->pattern['posPost']; + return array($prefix, $postfix); + } + + /** + * Set the format pattern for positive values. + * The positive pattern is composed of a prefix, and postfix in the form + * array(prefix, postfix). + * @param arary positive pattern. + */ + function setPositivePattern($pattern) + { + $this->pattern['posPref'] = $pattern[0]; + $this->pattern['posPost'] = $pattern[1]; + } + + /** + * Gets the string to use as the currency symbol. + * @return string currency symbol. + */ + function getCurrencySymbol($currency='USD') + { + if(isset($this->pattern['symbol'])) + return $this->pattern['symbol']; + else + return $this->data['Currencies'][$currency][0]; + } + + + /** + * Set the string to use as the currency symbol. + * @param string currency symbol. + */ + function setCurrencySymbol($symbol) + { + $this->pattern['symbol'] = $symbol; + } + + /** + * Gets the string that represents negative infinity. + * @return string negative infinity. + */ + function getNegativeInfinitySymbol() + { + return $this->pattern['negInfty']; + } + + /** + * Set the string that represents negative infinity. + * @param string negative infinity. + */ + function setNegativeInfinitySymbol($value) + { + $this->pattern['negInfty'] = $value; + } + + /** + * Gets the string that represents positive infinity. + * @return string positive infinity. + */ + function getPositiveInfinitySymbol() + { + return $this->pattern['posInfty']; + } + + /** + * Set the string that represents positive infinity. + * @param string positive infinity. + */ + function setPositiveInfinitySymbol($value) + { + $this->pattern['posInfty'] = $value; + } + + /** + * Gets the string that denotes that the associated number is negative. + * @return string negative sign. + */ + function getNegativeSign() + { + return $this->data['NumberElements'][6]; + } + + /** + * Set the string that denotes that the associated number is negative. + * @param string negative sign. + */ + function setNegativeSign($value) + { + $this->data['NumberElements'][6] = $value; + } + + /** + * Gets the string that denotes that the associated number is positive. + * @return string positive sign. + */ + function getPositiveSign() + { + return $this->data['NumberElements'][11]; + } + + /** + * Set the string that denotes that the associated number is positive. + * @param string positive sign. + */ + function setPositiveSign($value) + { + $this->data['NumberElements'][11] = $value; + } + + /** + * Gets the string that represents the IEEE NaN (not a number) value. + * @return string NaN symbol. + */ + function getNaNSymbol() + { + return $this->data['NumberElements'][10]; + } + + /** + * Set the string that represents the IEEE NaN (not a number) value. + * @param string NaN symbol. + */ + function setNaNSymbol($value) + { + $this->data['NumberElements'][10] = $value; + } + + /** + * Gets the string to use as the percent symbol. + * @return string percent symbol. + */ + function getPercentSymbol() + { + return $this->data['NumberElements'][3]; + } + + /** + * Set the string to use as the percent symbol. + * @param string percent symbol. + */ + function setPercentSymbol($value) + { + $this->data['NumberElements'][3] = $value; + } + + /** + * Gets the string to use as the per mille symbol. + * @return string percent symbol. + */ + function getPerMilleSymbol() + { + return $this->data['NumberElements'][8]; + } + + /** + * Set the string to use as the per mille symbol. + * @param string percent symbol. + */ + function setPerMilleSymbol($value) + { + $this->data['NumberElements'][8] = $value; + } +} + diff --git a/framework/I18N/core/TCache_Lite.php b/framework/I18N/core/TCache_Lite.php index 6ef78852..665ca469 100644 --- a/framework/I18N/core/TCache_Lite.php +++ b/framework/I18N/core/TCache_Lite.php @@ -1,629 +1,629 @@ - - * @version $Revision: 1.3 $ $Date: 2005/10/09 10:24:12 $ - * @package System.I18N.core - */ - -/** -* Fast, light and safe Cache Class -* -* TCache_Lite is a fast, light and safe cache system. It's optimized -* for file containers. It is fast and safe (because it uses file -* locking and/or anti-corruption tests). -* -* There are some examples in the 'docs/examples' file -* Technical choices are described in the 'docs/technical' file -* -* A tutorial is available in english at this url : -* http://www.pearfr.org/index.php/en/article/cache_lite -* (big thanks to Pierre-Alain Joye for the translation) -* -* The same tutorial is also available in french at this url : -* http://www.pearfr.org/index.php/fr/article/cache_lite -* -* Memory Caching is from an original idea of -* Mike BENOIT -* -* @package System.I18N.core -* @version $Id$ -* @author Fabien MARTY -* @copyright 1997-2005 The PHP Group -* @license http://www.gnu.org/copyleft/lesser.html GNU LGPL -* @link http://pear.php.net/package/Cache_Lite -*/ -class TCache_Lite -{ - - // --- Private properties --- - - /** - * Directory where to put the cache files - * (make sure to add a trailing slash) - * - * @var string $_cacheDir - */ - protected $_cacheDir = '/tmp/'; - - /** - * Enable / disable caching - * - * (can be very usefull for the debug of cached scripts) - * - * @var boolean $_caching - */ - protected $_caching = true; - - /** - * Cache lifetime (in seconds) - * - * @var int $_lifeTime - */ - protected $_lifeTime = 3600; - - /** - * Enable / disable fileLocking - * - * (can avoid cache corruption under bad circumstances) - * - * @var boolean $_fileLocking - */ - protected $_fileLocking = true; - - /** - * Timestamp of the last valid cache - * - * @var int $_refreshTime - */ - protected $_refreshTime; - - /** - * File name (with path) - * - * @var string $_file - */ - protected $_file; - - /** - * Enable / disable write control (the cache is read just after writing - * to detect corrupt entries) - * - * Enable write control will lightly slow the cache writing but not the - * cache reading. Write control can detect some corrupt cache files but - * maybe it's not a perfect control - * - * @var boolean $_writeControl - */ - protected $_writeControl = true; - - /** - * Enable / disable read control - * - * If enabled, a control key is embeded in cache file and this key is - * compared with the one calculated after the reading. - * - * @var boolean $_writeControl - */ - protected $_readControl = true; - - /** - * Type of read control (only if read control is enabled) - * - * Available values are : - * 'md5' for a md5 hash control (best but slowest) - * 'crc32' for a crc32 hash control (lightly less safe but faster, - * better choice) - * 'strlen' for a length only test (fastest) - * - * @var boolean $_readControlType - */ - protected $_readControlType = 'crc32'; - - /** - * Current cache id - * - * @var string $_id - */ - protected $_id; - - /** - * Current cache group - * - * @var string $_group - */ - protected $_group; - - /** - * Enable / Disable "Memory Caching" - * - * NB : There is no lifetime for memory caching ! - * - * @var boolean $_memoryCaching - */ - protected $_memoryCaching = false; - - /** - * Enable / Disable "Only Memory Caching" - * (be carefull, memory caching is "beta quality") - * - * @var boolean $_onlyMemoryCaching - */ - protected $_onlyMemoryCaching = false; - - /** - * Memory caching array - * - * @var array $_memoryCachingArray - */ - protected $_memoryCachingArray = array(); - - /** - * Memory caching counter - * - * @var int $memoryCachingCounter - */ - protected $_memoryCachingCounter = 0; - - /** - * Memory caching limit - * - * @var int $memoryCachingLimit - */ - protected $_memoryCachingLimit = 1000; - - /** - * File Name protection - * - * if set to true, you can use any cache id or group name - * if set to false, it can be faster but cache ids and group names - * will be used directly in cache file names so be carefull with - * special characters... - * - * @var boolean $fileNameProtection - */ - protected $_fileNameProtection = true; - - /** - * Enable / disable automatic serialization - * - * it can be used to save directly datas which aren't strings - * (but it's slower) - * - * @var boolean $_serialize - */ - protected $_automaticSerialization = false; - - // --- Public methods --- - - /** - * Constructor - * - * $options is an assoc. Available options are : - * $options = array( - * 'cacheDir' => directory where to put the cache files (string), - * 'caching' => enable / disable caching (boolean), - * 'lifeTime' => cache lifetime in seconds (int), - * 'fileLocking' => enable / disable fileLocking (boolean), - * 'writeControl' => enable / disable write control (boolean), - * 'readControl' => enable / disable read control (boolean), - * 'readControlType' => type of read control 'crc32', 'md5', 'strlen', - * 'memoryCaching' => enable / disable memory caching (boolean), - * 'onlyMemoryCaching' => enable / disable only memory caching (boolean), - * 'memoryCachingLimit' => max nbr of records in memory caching (int), - * 'fileNameProtection' => enable / disable file name protection (boolean), - * 'automaticSerialization' => enable / disable serialization (boolean) - * ); - * - * @param array $options options - * @access public - */ - function TCache_Lite($options = array(null)) - { - $availableOptions = array( 'automaticSerialization', - 'fileNameProtection', - 'memoryCaching', - 'onlyMemoryCaching', - 'memoryCachingLimit', - 'cacheDir', - 'caching', - 'lifeTime', - 'fileLocking', - 'writeControl', - 'readControl', - 'readControlType'); - foreach($options as $key => $value) { - if(in_array($key, $availableOptions)) { - $property = '_'.$key; - $this->$property = $value; - } - } - $this->_refreshTime = time() - $this->_lifeTime; - } - - /** - * Test if a cache is available and (if yes) return it - * - * @param string $id cache id - * @param string $group name of the cache group - * @param boolean $doNotTestCacheValidity if set to true, the cache - * validity won't be tested - * @return string data of the cache (or false if no cache available) - * @access public - */ - function get($id, $group = 'default', $doNotTestCacheValidity = false) - { - $this->_id = $id; - $this->_group = $group; - $data = false; - if ($this->_caching) { - $this->_setFileName($id, $group); - if ($this->_memoryCaching) { - if (isset($this->_memoryCachingArray[$this->_file])) { - if ($this->_automaticSerialization) { - return unserialize( - $this->_memoryCachingArray[$this->_file]); - } else { - return $this->_memoryCachingArray[$this->_file]; - } - } else { - if ($this->_onlyMemoryCaching) { - return false; - } - } - } - if ($doNotTestCacheValidity) { - if (file_exists($this->_file)) { - $data = $this->_read(); - } - } else { - if (@filemtime($this->_file) > $this->_refreshTime) { - $data = $this->_read(); - } - } - if (($data) and ($this->_memoryCaching)) { - $this->_memoryCacheAdd($this->_file, $data); - } - if ($this->_automaticSerialization && is_string($data)) { - $data = unserialize($data); - } - return $data; - } - return false; - } - - /** - * Save some data in a cache file - * - * @param string $data data to put in cache (can be another type than strings - * if automaticSerialization is on) - * @param string $id cache id - * @param string $group name of the cache group - * @return boolean true if no problem - * @access public - */ - function save($data, $id = null, $group = 'default') - { - if ($this->_caching) { - if ($this->_automaticSerialization) { - $data = serialize($data); - } - if (isset($id)) { - $this->_setFileName($id, $group); - } - if ($this->_memoryCaching) { - $this->_memoryCacheAdd($this->_file, $data); - if ($this->_onlyMemoryCaching) { - return true; - } - } - if ($this->_writeControl) { - if (!$this->_writeAndControl($data)) { - @touch($this->_file, time() - 2*abs($this->_lifeTime)); - return false; - } else { - return true; - } - } else { - return $this->_write($data); - } - } - return false; - } - - /** - * Remove a cache file - * - * @param string $id cache id - * @param string $group name of the cache group - * @return boolean true if no problem - * @access public - */ - function remove($id, $group = 'default') - { - $this->_setFileName($id, $group); - if (!@unlink($this->_file)) { - $this->raiseError('TCache_Lite : Unable to remove cache !', -3); - return false; - } - return true; - } - - /** - * Clean the cache - * - * if no group is specified all cache files will be destroyed - * else only cache files of the specified group will be destroyed - * - * @param string $group name of the cache group - * @return boolean true if no problem - * @access public - */ - function clean($group = false) - { - if ($this->_fileNameProtection) { - $motif = ($group) ? 'cache_'.md5($group).'_' : 'cache_'; - } else { - $motif = ($group) ? 'cache_'.$group.'_' : 'cache_'; - } - if ($this->_memoryCaching) { - while (list($key, $value) = each($this->_memoryCaching)) { - if (strpos($key, $motif, 0)) { - unset($this->_memoryCaching[$key]); - $this->_memoryCachingCounter = - $this->_memoryCachingCounter - 1; - } - } - if ($this->_onlyMemoryCaching) { - return true; - } - } - if (!($dh = opendir($this->_cacheDir))) { - $this->raiseError('TCache_Lite : Unable to open cache directory !'); - return false; - } - while ($file = readdir($dh)) { - if (($file != '.') && ($file != '..')) { - $file = $this->_cacheDir . $file; - if (is_file($file)) { - if (strpos($file, $motif, 0)) { - if (!@unlink($file)) { - $this->raiseError('Cache_Lite : Unable to remove cache !', -3); - return false; - } - } - } - } - } - return true; - } - - /** - * Set a new life time - * - * @param int $newLifeTime new life time (in seconds) - * @access public - */ - function setLifeTime($newLifeTime) - { - $this->_lifeTime = $newLifeTime; - $this->_refreshTime = time() - $newLifeTime; - } - - /** - * - * @access public - */ - function saveMemoryCachingState($id, $group = 'default') - { - if ($this->_caching) { - $array = array( - 'counter' => $this->_memoryCachingCounter, - 'array' => $this->_memoryCachingState - ); - $data = serialize($array); - $this->save($data, $id, $group); - } - } - - /** - * - * @access public - */ - function getMemoryCachingState($id, $group = 'default', - $doNotTestCacheValidity = false) - { - if ($this->_caching) { - if ($data = $this->get($id, $group, $doNotTestCacheValidity)) - { - $array = unserialize($data); - $this->_memoryCachingCounter = $array['counter']; - $this->_memoryCachingArray = $array['array']; - } - } - } - - /** - * Return the cache last modification time - * - * BE CAREFUL : THIS METHOD IS FOR HACKING ONLY ! - * - * @return int last modification time - */ - function lastModified() { - return filemtime($this->cache->_file); - } - - /** - * Trigger a PEAR error - * - * To improve performances, the PEAR.php file is included dynamically. - * The file is so included only when an error is triggered. So, in most - * cases, the file isn't included and perfs are much better. - * - * @param string $msg error message - * @param int $code error code - * @access public - */ - function raiseError($msg, $code) - { - throw new Exception($msg); - } - - // --- Private methods --- - - /** - * - * @access private - */ - function _memoryCacheAdd($id, $data) - { - $this->_memoryCachingArray[$this->_file] = $data; - if ($this->_memoryCachingCounter >= $this->_memoryCachingLimit) { - list($key, $value) = each($this->_memoryCachingArray); - unset($this->_memoryCachingArray[$key]); - } else { - $this->_memoryCachingCounter = $this->_memoryCachingCounter + 1; - } - } - - /** - * Make a file name (with path) - * - * @param string $id cache id - * @param string $group name of the group - * @access private - */ - function _setFileName($id, $group) - { - if ($this->_fileNameProtection) { - $this->_file = ($this->_cacheDir.'cache_'.md5($group).'_' - .md5($id)); - } else { - $this->_file = $this->_cacheDir.'cache_'.$group.'_'.$id; - } - } - - function getCacheFile() - { - return $this->_file; - } - - /** - * Read the cache file and return the content - * - * @return string content of the cache file - * @access private - */ - function _read() - { - $fp = @fopen($this->_file, "rb"); - if ($this->_fileLocking) @flock($fp, LOCK_SH); - if ($fp) { - // because the filesize can be cached by PHP itself... - clearstatcache(); - $length = @filesize($this->_file); - if(version_compare(PHP_VERSION, '5.3.0', 'lt')) - { - $mqr = get_magic_quotes_runtime(); - set_magic_quotes_runtime(0); - } - if ($this->_readControl) { - $hashControl = @fread($fp, 32); - $length = $length - 32; - } - $data = @fread($fp, $length); - if(isset($mqr)) - set_magic_quotes_runtime($mqr); - if ($this->_fileLocking) @flock($fp, LOCK_UN); - @fclose($fp); - if ($this->_readControl) { - $hashData = $this->_hash($data, $this->_readControlType); - if ($hashData != $hashControl) { - @touch($this->_file, time() - 2*abs($this->_lifeTime)); - return false; - } - } - return $data; - } - $this->raiseError('Cache_Lite : Unable to read cache !', -2); - return false; - } - - /** - * Write the given data in the cache file - * - * @param string $data data to put in cache - * @return boolean true if ok - * @access private - */ - function _write($data) - { - $fp = @fopen($this->_file, "wb"); - if ($fp) { - if ($this->_fileLocking) @flock($fp, LOCK_EX); - if ($this->_readControl) { - @fwrite($fp, $this->_hash($data, $this->_readControlType), 32); - } - $len = strlen($data); - @fwrite($fp, $data, $len); - if ($this->_fileLocking) @flock($fp, LOCK_UN); - @fclose($fp); - return true; - } - $this->raiseError('Cache_Lite : Unable to write cache !', -1); - return false; - } - - /** - * Write the given data in the cache file and control it just after to avoid - * corrupted cache entries - * - * @param string $data data to put in cache - * @return boolean true if the test is ok - * @access private - */ - function _writeAndControl($data) - { - $this->_write($data); - $dataRead = $this->_read($data); - return ($dataRead==$data); - } - - /** - * Make a control key with the string containing datas - * - * @param string $data data - * @param string $controlType type of control 'md5', 'crc32' or 'strlen' - * @return string control key - * @access private - */ - function _hash($data, $controlType) - { - switch ($controlType) { - case 'md5': - return md5($data); - case 'crc32': - return sprintf('% 32d', crc32($data)); - case 'strlen': - return sprintf('% 32d', strlen($data)); - default: - $this->raiseError('Unknown controlType ! '. - '(available values are only \'md5\', \'crc32\', \'strlen\')', -5); - } - } - -} - -?> + + * @version $Revision: 1.3 $ $Date: 2005/10/09 10:24:12 $ + * @package System.I18N.core + */ + +/** +* Fast, light and safe Cache Class +* +* TCache_Lite is a fast, light and safe cache system. It's optimized +* for file containers. It is fast and safe (because it uses file +* locking and/or anti-corruption tests). +* +* There are some examples in the 'docs/examples' file +* Technical choices are described in the 'docs/technical' file +* +* A tutorial is available in english at this url : +* http://www.pearfr.org/index.php/en/article/cache_lite +* (big thanks to Pierre-Alain Joye for the translation) +* +* The same tutorial is also available in french at this url : +* http://www.pearfr.org/index.php/fr/article/cache_lite +* +* Memory Caching is from an original idea of +* Mike BENOIT +* +* @package System.I18N.core +* @version $Id$ +* @author Fabien MARTY +* @copyright 1997-2005 The PHP Group +* @license http://www.gnu.org/copyleft/lesser.html GNU LGPL +* @link http://pear.php.net/package/Cache_Lite +*/ +class TCache_Lite +{ + + // --- Private properties --- + + /** + * Directory where to put the cache files + * (make sure to add a trailing slash) + * + * @var string $_cacheDir + */ + protected $_cacheDir = '/tmp/'; + + /** + * Enable / disable caching + * + * (can be very usefull for the debug of cached scripts) + * + * @var boolean $_caching + */ + protected $_caching = true; + + /** + * Cache lifetime (in seconds) + * + * @var int $_lifeTime + */ + protected $_lifeTime = 3600; + + /** + * Enable / disable fileLocking + * + * (can avoid cache corruption under bad circumstances) + * + * @var boolean $_fileLocking + */ + protected $_fileLocking = true; + + /** + * Timestamp of the last valid cache + * + * @var int $_refreshTime + */ + protected $_refreshTime; + + /** + * File name (with path) + * + * @var string $_file + */ + protected $_file; + + /** + * Enable / disable write control (the cache is read just after writing + * to detect corrupt entries) + * + * Enable write control will lightly slow the cache writing but not the + * cache reading. Write control can detect some corrupt cache files but + * maybe it's not a perfect control + * + * @var boolean $_writeControl + */ + protected $_writeControl = true; + + /** + * Enable / disable read control + * + * If enabled, a control key is embeded in cache file and this key is + * compared with the one calculated after the reading. + * + * @var boolean $_writeControl + */ + protected $_readControl = true; + + /** + * Type of read control (only if read control is enabled) + * + * Available values are : + * 'md5' for a md5 hash control (best but slowest) + * 'crc32' for a crc32 hash control (lightly less safe but faster, + * better choice) + * 'strlen' for a length only test (fastest) + * + * @var boolean $_readControlType + */ + protected $_readControlType = 'crc32'; + + /** + * Current cache id + * + * @var string $_id + */ + protected $_id; + + /** + * Current cache group + * + * @var string $_group + */ + protected $_group; + + /** + * Enable / Disable "Memory Caching" + * + * NB : There is no lifetime for memory caching ! + * + * @var boolean $_memoryCaching + */ + protected $_memoryCaching = false; + + /** + * Enable / Disable "Only Memory Caching" + * (be carefull, memory caching is "beta quality") + * + * @var boolean $_onlyMemoryCaching + */ + protected $_onlyMemoryCaching = false; + + /** + * Memory caching array + * + * @var array $_memoryCachingArray + */ + protected $_memoryCachingArray = array(); + + /** + * Memory caching counter + * + * @var int $memoryCachingCounter + */ + protected $_memoryCachingCounter = 0; + + /** + * Memory caching limit + * + * @var int $memoryCachingLimit + */ + protected $_memoryCachingLimit = 1000; + + /** + * File Name protection + * + * if set to true, you can use any cache id or group name + * if set to false, it can be faster but cache ids and group names + * will be used directly in cache file names so be carefull with + * special characters... + * + * @var boolean $fileNameProtection + */ + protected $_fileNameProtection = true; + + /** + * Enable / disable automatic serialization + * + * it can be used to save directly datas which aren't strings + * (but it's slower) + * + * @var boolean $_serialize + */ + protected $_automaticSerialization = false; + + // --- Public methods --- + + /** + * Constructor + * + * $options is an assoc. Available options are : + * $options = array( + * 'cacheDir' => directory where to put the cache files (string), + * 'caching' => enable / disable caching (boolean), + * 'lifeTime' => cache lifetime in seconds (int), + * 'fileLocking' => enable / disable fileLocking (boolean), + * 'writeControl' => enable / disable write control (boolean), + * 'readControl' => enable / disable read control (boolean), + * 'readControlType' => type of read control 'crc32', 'md5', 'strlen', + * 'memoryCaching' => enable / disable memory caching (boolean), + * 'onlyMemoryCaching' => enable / disable only memory caching (boolean), + * 'memoryCachingLimit' => max nbr of records in memory caching (int), + * 'fileNameProtection' => enable / disable file name protection (boolean), + * 'automaticSerialization' => enable / disable serialization (boolean) + * ); + * + * @param array $options options + * @access public + */ + function TCache_Lite($options = array(null)) + { + $availableOptions = array( 'automaticSerialization', + 'fileNameProtection', + 'memoryCaching', + 'onlyMemoryCaching', + 'memoryCachingLimit', + 'cacheDir', + 'caching', + 'lifeTime', + 'fileLocking', + 'writeControl', + 'readControl', + 'readControlType'); + foreach($options as $key => $value) { + if(in_array($key, $availableOptions)) { + $property = '_'.$key; + $this->$property = $value; + } + } + $this->_refreshTime = time() - $this->_lifeTime; + } + + /** + * Test if a cache is available and (if yes) return it + * + * @param string $id cache id + * @param string $group name of the cache group + * @param boolean $doNotTestCacheValidity if set to true, the cache + * validity won't be tested + * @return string data of the cache (or false if no cache available) + * @access public + */ + function get($id, $group = 'default', $doNotTestCacheValidity = false) + { + $this->_id = $id; + $this->_group = $group; + $data = false; + if ($this->_caching) { + $this->_setFileName($id, $group); + if ($this->_memoryCaching) { + if (isset($this->_memoryCachingArray[$this->_file])) { + if ($this->_automaticSerialization) { + return unserialize( + $this->_memoryCachingArray[$this->_file]); + } else { + return $this->_memoryCachingArray[$this->_file]; + } + } else { + if ($this->_onlyMemoryCaching) { + return false; + } + } + } + if ($doNotTestCacheValidity) { + if (file_exists($this->_file)) { + $data = $this->_read(); + } + } else { + if (@filemtime($this->_file) > $this->_refreshTime) { + $data = $this->_read(); + } + } + if (($data) and ($this->_memoryCaching)) { + $this->_memoryCacheAdd($this->_file, $data); + } + if ($this->_automaticSerialization && is_string($data)) { + $data = unserialize($data); + } + return $data; + } + return false; + } + + /** + * Save some data in a cache file + * + * @param string $data data to put in cache (can be another type than strings + * if automaticSerialization is on) + * @param string $id cache id + * @param string $group name of the cache group + * @return boolean true if no problem + * @access public + */ + function save($data, $id = null, $group = 'default') + { + if ($this->_caching) { + if ($this->_automaticSerialization) { + $data = serialize($data); + } + if (isset($id)) { + $this->_setFileName($id, $group); + } + if ($this->_memoryCaching) { + $this->_memoryCacheAdd($this->_file, $data); + if ($this->_onlyMemoryCaching) { + return true; + } + } + if ($this->_writeControl) { + if (!$this->_writeAndControl($data)) { + @touch($this->_file, time() - 2*abs($this->_lifeTime)); + return false; + } else { + return true; + } + } else { + return $this->_write($data); + } + } + return false; + } + + /** + * Remove a cache file + * + * @param string $id cache id + * @param string $group name of the cache group + * @return boolean true if no problem + * @access public + */ + function remove($id, $group = 'default') + { + $this->_setFileName($id, $group); + if (!@unlink($this->_file)) { + $this->raiseError('TCache_Lite : Unable to remove cache !', -3); + return false; + } + return true; + } + + /** + * Clean the cache + * + * if no group is specified all cache files will be destroyed + * else only cache files of the specified group will be destroyed + * + * @param string $group name of the cache group + * @return boolean true if no problem + * @access public + */ + function clean($group = false) + { + if ($this->_fileNameProtection) { + $motif = ($group) ? 'cache_'.md5($group).'_' : 'cache_'; + } else { + $motif = ($group) ? 'cache_'.$group.'_' : 'cache_'; + } + if ($this->_memoryCaching) { + while (list($key, $value) = each($this->_memoryCaching)) { + if (strpos($key, $motif, 0)) { + unset($this->_memoryCaching[$key]); + $this->_memoryCachingCounter = + $this->_memoryCachingCounter - 1; + } + } + if ($this->_onlyMemoryCaching) { + return true; + } + } + if (!($dh = opendir($this->_cacheDir))) { + $this->raiseError('TCache_Lite : Unable to open cache directory !'); + return false; + } + while ($file = readdir($dh)) { + if (($file != '.') && ($file != '..')) { + $file = $this->_cacheDir . $file; + if (is_file($file)) { + if (strpos($file, $motif, 0)) { + if (!@unlink($file)) { + $this->raiseError('Cache_Lite : Unable to remove cache !', -3); + return false; + } + } + } + } + } + return true; + } + + /** + * Set a new life time + * + * @param int $newLifeTime new life time (in seconds) + * @access public + */ + function setLifeTime($newLifeTime) + { + $this->_lifeTime = $newLifeTime; + $this->_refreshTime = time() - $newLifeTime; + } + + /** + * + * @access public + */ + function saveMemoryCachingState($id, $group = 'default') + { + if ($this->_caching) { + $array = array( + 'counter' => $this->_memoryCachingCounter, + 'array' => $this->_memoryCachingState + ); + $data = serialize($array); + $this->save($data, $id, $group); + } + } + + /** + * + * @access public + */ + function getMemoryCachingState($id, $group = 'default', + $doNotTestCacheValidity = false) + { + if ($this->_caching) { + if ($data = $this->get($id, $group, $doNotTestCacheValidity)) + { + $array = unserialize($data); + $this->_memoryCachingCounter = $array['counter']; + $this->_memoryCachingArray = $array['array']; + } + } + } + + /** + * Return the cache last modification time + * + * BE CAREFUL : THIS METHOD IS FOR HACKING ONLY ! + * + * @return int last modification time + */ + function lastModified() { + return filemtime($this->cache->_file); + } + + /** + * Trigger a PEAR error + * + * To improve performances, the PEAR.php file is included dynamically. + * The file is so included only when an error is triggered. So, in most + * cases, the file isn't included and perfs are much better. + * + * @param string $msg error message + * @param int $code error code + * @access public + */ + function raiseError($msg, $code) + { + throw new Exception($msg); + } + + // --- Private methods --- + + /** + * + * @access private + */ + function _memoryCacheAdd($id, $data) + { + $this->_memoryCachingArray[$this->_file] = $data; + if ($this->_memoryCachingCounter >= $this->_memoryCachingLimit) { + list($key, $value) = each($this->_memoryCachingArray); + unset($this->_memoryCachingArray[$key]); + } else { + $this->_memoryCachingCounter = $this->_memoryCachingCounter + 1; + } + } + + /** + * Make a file name (with path) + * + * @param string $id cache id + * @param string $group name of the group + * @access private + */ + function _setFileName($id, $group) + { + if ($this->_fileNameProtection) { + $this->_file = ($this->_cacheDir.'cache_'.md5($group).'_' + .md5($id)); + } else { + $this->_file = $this->_cacheDir.'cache_'.$group.'_'.$id; + } + } + + function getCacheFile() + { + return $this->_file; + } + + /** + * Read the cache file and return the content + * + * @return string content of the cache file + * @access private + */ + function _read() + { + $fp = @fopen($this->_file, "rb"); + if ($this->_fileLocking) @flock($fp, LOCK_SH); + if ($fp) { + // because the filesize can be cached by PHP itself... + clearstatcache(); + $length = @filesize($this->_file); + if(version_compare(PHP_VERSION, '5.3.0', 'lt')) + { + $mqr = get_magic_quotes_runtime(); + set_magic_quotes_runtime(0); + } + if ($this->_readControl) { + $hashControl = @fread($fp, 32); + $length = $length - 32; + } + $data = @fread($fp, $length); + if(isset($mqr)) + set_magic_quotes_runtime($mqr); + if ($this->_fileLocking) @flock($fp, LOCK_UN); + @fclose($fp); + if ($this->_readControl) { + $hashData = $this->_hash($data, $this->_readControlType); + if ($hashData != $hashControl) { + @touch($this->_file, time() - 2*abs($this->_lifeTime)); + return false; + } + } + return $data; + } + $this->raiseError('Cache_Lite : Unable to read cache !', -2); + return false; + } + + /** + * Write the given data in the cache file + * + * @param string $data data to put in cache + * @return boolean true if ok + * @access private + */ + function _write($data) + { + $fp = @fopen($this->_file, "wb"); + if ($fp) { + if ($this->_fileLocking) @flock($fp, LOCK_EX); + if ($this->_readControl) { + @fwrite($fp, $this->_hash($data, $this->_readControlType), 32); + } + $len = strlen($data); + @fwrite($fp, $data, $len); + if ($this->_fileLocking) @flock($fp, LOCK_UN); + @fclose($fp); + return true; + } + $this->raiseError('Cache_Lite : Unable to write cache !', -1); + return false; + } + + /** + * Write the given data in the cache file and control it just after to avoid + * corrupted cache entries + * + * @param string $data data to put in cache + * @return boolean true if the test is ok + * @access private + */ + function _writeAndControl($data) + { + $this->_write($data); + $dataRead = $this->_read($data); + return ($dataRead==$data); + } + + /** + * Make a control key with the string containing datas + * + * @param string $data data + * @param string $controlType type of control 'md5', 'crc32' or 'strlen' + * @return string control key + * @access private + */ + function _hash($data, $controlType) + { + switch ($controlType) { + case 'md5': + return md5($data); + case 'crc32': + return sprintf('% 32d', crc32($data)); + case 'strlen': + return sprintf('% 32d', strlen($data)); + default: + $this->raiseError('Unknown controlType ! '. + '(available values are only \'md5\', \'crc32\', \'strlen\')', -5); + } + } + +} + +?> diff --git a/framework/I18N/core/util.php b/framework/I18N/core/util.php index c0092f19..c618b33a 100644 --- a/framework/I18N/core/util.php +++ b/framework/I18N/core/util.php @@ -1,188 +1,188 @@ - - * The latest version of PRADO can be obtained from: - * {@link http://prado.sourceforge.net/} - * - * @author Wei Zhuo - * @version $Revision: 1.3 $ $Date: 2005/08/27 03:21:12 $ - * @package System.I18N.core - */ - - - /** - * For a given DSN (database connection string), return some information - * about the DSN. This function comes from PEAR's DB package. - * - * LICENSE: This source file is subject to version 3.0 of the PHP license - * that is available through the world-wide-web at the following URI: - * http://www.php.net/license/3_0.txt. If you did not receive a copy of - * the PHP License and are unable to obtain it through the web, please - * send a note to license@php.net so we can mail you a copy immediately. - * - * @param string DSN format, similar to PEAR's DB - * @return array DSN information. - * @author Stig Bakken - * @author Tomas V.V.Cox - * @author Daniel Convissor - * @copyright 1997-2005 The PHP Group - * @license http://www.php.net/license/3_0.txt PHP License 3.0 - * @link http://pear.php.net/package/DB - */ - function parseDSN($dsn) - { - if (is_array($dsn)) { - return $dsn; - } - - $parsed = array( - 'phptype' => false, - 'dbsyntax' => false, - 'username' => false, - 'password' => false, - 'protocol' => false, - 'hostspec' => false, - 'port' => false, - 'socket' => false, - 'database' => false - ); - - // Find phptype and dbsyntax - if (($pos = strpos($dsn, '://')) !== false) { - $str = substr($dsn, 0, $pos); - $dsn = substr($dsn, $pos + 3); - } else { - $str = $dsn; - $dsn = null; - } - - // Get phptype and dbsyntax - // $str => phptype(dbsyntax) - if (preg_match('|^(.+?)\((.*?)\)$|', $str, $arr)) { - $parsed['phptype'] = $arr[1]; - $parsed['dbsyntax'] = (empty($arr[2])) ? $arr[1] : $arr[2]; - } else { - $parsed['phptype'] = $str; - $parsed['dbsyntax'] = $str; - } - - if (empty($dsn)) { - return $parsed; - } - - // Get (if found): username and password - // $dsn => username:password@protocol+hostspec/database - if (($at = strrpos($dsn,'@')) !== false) { - $str = substr($dsn, 0, $at); - $dsn = substr($dsn, $at + 1); - if (($pos = strpos($str, ':')) !== false) { - $parsed['username'] = rawurldecode(substr($str, 0, $pos)); - $parsed['password'] = rawurldecode(substr($str, $pos + 1)); - } else { - $parsed['username'] = rawurldecode($str); - } - } - - // Find protocol and hostspec - - // $dsn => proto(proto_opts)/database - if (preg_match('|^([^(]+)\((.*?)\)/?(.*?)$|', $dsn, $match)) { - $proto = $match[1]; - $proto_opts = (!empty($match[2])) ? $match[2] : false; - $dsn = $match[3]; - - // $dsn => protocol+hostspec/database (old format) - } else { - if (strpos($dsn, '+') !== false) { - list($proto, $dsn) = explode('+', $dsn, 2); - } - if (strpos($dsn, '/') !== false) { - list($proto_opts, $dsn) = explode('/', $dsn, 2); - } else { - $proto_opts = $dsn; - $dsn = null; - } - } - - // process the different protocol options - $parsed['protocol'] = (!empty($proto)) ? $proto : 'tcp'; - $proto_opts = rawurldecode($proto_opts); - if ($parsed['protocol'] == 'tcp') { - if (strpos($proto_opts, ':') !== false) { - list($parsed['hostspec'], $parsed['port']) = explode(':', $proto_opts); - } else { - $parsed['hostspec'] = $proto_opts; - } - } elseif ($parsed['protocol'] == 'unix') { - $parsed['socket'] = $proto_opts; - } - - // Get dabase if any - // $dsn => database - if (!empty($dsn)) { - // /database - if (($pos = strpos($dsn, '?')) === false) { - $parsed['database'] = $dsn; - // /database?param1=value1¶m2=value2 - } else { - $parsed['database'] = substr($dsn, 0, $pos); - $dsn = substr($dsn, $pos + 1); - if (strpos($dsn, '&') !== false) { - $opts = explode('&', $dsn); - } else { // database?param1=value1 - $opts = array($dsn); - } - foreach ($opts as $opt) { - list($key, $value) = explode('=', $opt); - if (!isset($parsed[$key])) { // don't allow params overwrite - $parsed[$key] = rawurldecode($value); - } - } - } - } - - return $parsed; - } - - - /** - * Convert strings to UTF-8 via iconv. NB, the result may not by UTF-8 - * if the conversion failed. - * @param string string to convert to UTF-8 - * @return string UTF-8 encoded string, original string if iconv failed. - */ - function I18N_toUTF8($string, $from) - { - if($from != 'UTF-8') - { - $s = iconv($from,'UTF-8',$string); //to UTF-8 - return $s !== false ? $s : $string; //it could return false - } - return $string; - } - - /** - * Convert UTF-8 strings to a different encoding. NB. The result - * may not have been encoded if iconv fails. - * @param string the UTF-8 string for conversion - * @return string encoded string. - */ - function I18N_toEncoding($string, $to) - { - if($to != 'UTF-8') - { - $s = iconv('UTF-8', $to, $string); - return $s !== false ? $s : $string; - } - return $string; - } - + + * The latest version of PRADO can be obtained from: + * {@link http://prado.sourceforge.net/} + * + * @author Wei Zhuo + * @version $Revision: 1.3 $ $Date: 2005/08/27 03:21:12 $ + * @package System.I18N.core + */ + + + /** + * For a given DSN (database connection string), return some information + * about the DSN. This function comes from PEAR's DB package. + * + * LICENSE: This source file is subject to version 3.0 of the PHP license + * that is available through the world-wide-web at the following URI: + * http://www.php.net/license/3_0.txt. If you did not receive a copy of + * the PHP License and are unable to obtain it through the web, please + * send a note to license@php.net so we can mail you a copy immediately. + * + * @param string DSN format, similar to PEAR's DB + * @return array DSN information. + * @author Stig Bakken + * @author Tomas V.V.Cox + * @author Daniel Convissor + * @copyright 1997-2005 The PHP Group + * @license http://www.php.net/license/3_0.txt PHP License 3.0 + * @link http://pear.php.net/package/DB + */ + function parseDSN($dsn) + { + if (is_array($dsn)) { + return $dsn; + } + + $parsed = array( + 'phptype' => false, + 'dbsyntax' => false, + 'username' => false, + 'password' => false, + 'protocol' => false, + 'hostspec' => false, + 'port' => false, + 'socket' => false, + 'database' => false + ); + + // Find phptype and dbsyntax + if (($pos = strpos($dsn, '://')) !== false) { + $str = substr($dsn, 0, $pos); + $dsn = substr($dsn, $pos + 3); + } else { + $str = $dsn; + $dsn = null; + } + + // Get phptype and dbsyntax + // $str => phptype(dbsyntax) + if (preg_match('|^(.+?)\((.*?)\)$|', $str, $arr)) { + $parsed['phptype'] = $arr[1]; + $parsed['dbsyntax'] = (empty($arr[2])) ? $arr[1] : $arr[2]; + } else { + $parsed['phptype'] = $str; + $parsed['dbsyntax'] = $str; + } + + if (empty($dsn)) { + return $parsed; + } + + // Get (if found): username and password + // $dsn => username:password@protocol+hostspec/database + if (($at = strrpos($dsn,'@')) !== false) { + $str = substr($dsn, 0, $at); + $dsn = substr($dsn, $at + 1); + if (($pos = strpos($str, ':')) !== false) { + $parsed['username'] = rawurldecode(substr($str, 0, $pos)); + $parsed['password'] = rawurldecode(substr($str, $pos + 1)); + } else { + $parsed['username'] = rawurldecode($str); + } + } + + // Find protocol and hostspec + + // $dsn => proto(proto_opts)/database + if (preg_match('|^([^(]+)\((.*?)\)/?(.*?)$|', $dsn, $match)) { + $proto = $match[1]; + $proto_opts = (!empty($match[2])) ? $match[2] : false; + $dsn = $match[3]; + + // $dsn => protocol+hostspec/database (old format) + } else { + if (strpos($dsn, '+') !== false) { + list($proto, $dsn) = explode('+', $dsn, 2); + } + if (strpos($dsn, '/') !== false) { + list($proto_opts, $dsn) = explode('/', $dsn, 2); + } else { + $proto_opts = $dsn; + $dsn = null; + } + } + + // process the different protocol options + $parsed['protocol'] = (!empty($proto)) ? $proto : 'tcp'; + $proto_opts = rawurldecode($proto_opts); + if ($parsed['protocol'] == 'tcp') { + if (strpos($proto_opts, ':') !== false) { + list($parsed['hostspec'], $parsed['port']) = explode(':', $proto_opts); + } else { + $parsed['hostspec'] = $proto_opts; + } + } elseif ($parsed['protocol'] == 'unix') { + $parsed['socket'] = $proto_opts; + } + + // Get dabase if any + // $dsn => database + if (!empty($dsn)) { + // /database + if (($pos = strpos($dsn, '?')) === false) { + $parsed['database'] = $dsn; + // /database?param1=value1¶m2=value2 + } else { + $parsed['database'] = substr($dsn, 0, $pos); + $dsn = substr($dsn, $pos + 1); + if (strpos($dsn, '&') !== false) { + $opts = explode('&', $dsn); + } else { // database?param1=value1 + $opts = array($dsn); + } + foreach ($opts as $opt) { + list($key, $value) = explode('=', $opt); + if (!isset($parsed[$key])) { // don't allow params overwrite + $parsed[$key] = rawurldecode($value); + } + } + } + } + + return $parsed; + } + + + /** + * Convert strings to UTF-8 via iconv. NB, the result may not by UTF-8 + * if the conversion failed. + * @param string string to convert to UTF-8 + * @return string UTF-8 encoded string, original string if iconv failed. + */ + function I18N_toUTF8($string, $from) + { + if($from != 'UTF-8') + { + $s = iconv($from,'UTF-8',$string); //to UTF-8 + return $s !== false ? $s : $string; //it could return false + } + return $string; + } + + /** + * Convert UTF-8 strings to a different encoding. NB. The result + * may not have been encoded if iconv fails. + * @param string the UTF-8 string for conversion + * @return string encoded string. + */ + function I18N_toEncoding($string, $to) + { + if($to != 'UTF-8') + { + $s = iconv('UTF-8', $to, $string); + return $s !== false ? $s : $string; + } + return $string; + } + ?> \ No newline at end of file -- cgit v1.2.3