From 9cab8ae58a29ecf7387e6865aa170715caeabf04 Mon Sep 17 00:00:00 2001 From: Stefan Parviainen Date: Tue, 30 Dec 2014 19:57:12 +0100 Subject: Language names via intl library. Fixes #773 --- library/intl/src/Formatter/NumberFormatter.php | 406 +++++++++++++++++++++ .../src/Formatter/NumberFormatterInterface.php | 134 +++++++ 2 files changed, 540 insertions(+) create mode 100644 library/intl/src/Formatter/NumberFormatter.php create mode 100644 library/intl/src/Formatter/NumberFormatterInterface.php (limited to 'library/intl/src/Formatter') diff --git a/library/intl/src/Formatter/NumberFormatter.php b/library/intl/src/Formatter/NumberFormatter.php new file mode 100644 index 000000000..0b8e2ae59 --- /dev/null +++ b/library/intl/src/Formatter/NumberFormatter.php @@ -0,0 +1,406 @@ + array( + 0 => '٠', 1 => '١', 2 => '٢', 3 => '٣', 4 => '٤', + 5 => '٥', 6 => '٦', 7 => '٧', 8 => '٨', 9 => '٩', + ), + NumberFormatInterface::NUMBERING_SYSTEM_ARABIC_EXTENDED => array( + 0 => '۰', 1 => '۱', 2 => '۲', 3 => '۳', 4 => '۴', + 5 => '۵', 6 => '۶', 7 => '۷', 8 => '۸', 9 => '۹', + ), + NumberFormatInterface::NUMBERING_SYSTEM_BENGALI => array( + 0 => '০', 1 => '১', 2 => '২', 3 => '৩', 4 => '৪', + 5 => '৫', 6 => '৬', 7 => '৭', 8 => '৮', 9 => '৯', + ), + NumberFormatInterface::NUMBERING_SYSTEM_DEVANAGARI => array( + 0 => '०', 1 => '१', 2 => '२', 3 => '३', 4 => '४', + 5 => '५', 6 => '६', 7 => '७', 8 => '८', 9 => '९', + ), + ); + + /** + * Creaes a NumberFormatter instance. + * + * @param NumberFormatInterface $numberFormat The number format. + * @param int $style The formatting style. + * + * @throws InvalidArgumentException + */ + public function __construct(NumberFormatInterface $numberFormat, $style = self::DECIMAL) + { + $availablePatterns = array( + self::DECIMAL => $numberFormat->getDecimalPattern(), + self::PERCENT => $numberFormat->getPercentPattern(), + self::CURRENCY => $numberFormat->getCurrencyPattern(), + self::CURRENCY_ACCOUNTING => $numberFormat->getAccountingCurrencyPattern(), + ); + if (!array_key_exists($style, $availablePatterns)) { + // Unknown type. + throw new InvalidArgumentException('Unknown format style provided to NumberFormatter::__construct().'); + } + + // Split the selected pattern into positive and negative patterns. + $patterns = explode(';', $availablePatterns[$style]); + if (!isset($patterns[1])) { + // No explicit negative pattern was provided, construct it. + $patterns[1] = '-' . $patterns[0]; + } + + $this->numberFormat = $numberFormat; + $this->positivePattern = $patterns[0]; + $this->negativePattern = $patterns[1]; + $this->groupingUsed = (strpos($this->positivePattern, ',') !== false); + // This pattern has number groups, parse them. + if ($this->groupingUsed) { + preg_match('/#+0/', $this->positivePattern, $primaryGroupMatches); + $this->primaryGroupSize = $this->secondaryGroupSize = strlen($primaryGroupMatches[0]); + $numberGroups = explode(',', $this->positivePattern); + if (count($numberGroups) > 2) { + // This pattern has a distinct secondary group size. + $this->secondaryGroupSize = strlen($numberGroups[1]); + } + } + + // Initialize the fraction digit settings for decimal and percent + // styles only. The currency ones will default to the currency values. + if (in_array($style, array(self::DECIMAL, self::PERCENT))) { + $this->minimumFractionDigits = 0; + $this->maximumFractionDigits = 3; + } + $this->currencyDisplay = self::CURRENCY_DISPLAY_SYMBOL; + } + + /** + * {@inheritdoc} + */ + public function format($value) + { + if (!is_numeric($value)) { + $message = sprintf('The provided value "%s" must be a valid number or numeric string.', $value); + throw new InvalidArgumentException($message); + } + + // Ensure that the value is positive and has the right number of digits. + $negative = (bccomp('0', $value, 12) == 1); + $signMultiplier = $negative ? '-1' : '1'; + $value = bcdiv($value, $signMultiplier, $this->maximumFractionDigits); + // Split the number into major and minor digits. + $valueParts = explode('.', $value); + $majorDigits = $valueParts[0]; + // Account for maximumFractionDigits = 0, where the number won't + // have a decimal point, and $valueParts[1] won't be set. + $minorDigits = isset($valueParts[1]) ? $valueParts[1] : ''; + + if ($this->groupingUsed) { + // Reverse the major digits, since they are grouped from the right. + $majorDigits = array_reverse(str_split($majorDigits)); + // Group the major digits. + $groups = array(); + $groups[] = array_splice($majorDigits, 0, $this->primaryGroupSize); + while (!empty($majorDigits)) { + $groups[] = array_splice($majorDigits, 0, $this->secondaryGroupSize); + } + // Reverse the groups and the digits inside of them. + $groups = array_reverse($groups); + foreach ($groups as &$group) { + $group = implode(array_reverse($group)); + } + // Reconstruct the major digits. + $majorDigits = implode(',', $groups); + } + + if ($this->minimumFractionDigits < $this->maximumFractionDigits) { + // Strip any trailing zeroes. + $minorDigits = rtrim($minorDigits, '0'); + if (strlen($minorDigits) < $this->minimumFractionDigits) { + // Now there are too few digits, re-add trailing zeroes + // until the desired length is reached. + $neededZeroes = $this->minimumFractionDigits - strlen($minorDigits); + $minorDigits .= str_repeat('0', $neededZeroes); + } + } + + // Assemble the final number and insert it into the pattern. + $value = $minorDigits ? $majorDigits . '.' . $minorDigits : $majorDigits; + $pattern = $negative ? $this->negativePattern : $this->positivePattern; + $value = preg_replace('/#(?:[\.,]#+)*0(?:[,\.][0#]+)*/', $value, $pattern); + + // Localize the number. + $value = $this->replaceDigits($value); + $value = $this->replaceSymbols($value); + + return $value; + } + + /** + * {@inheritdoc} + */ + public function formatCurrency($value, CurrencyInterface $currency) + { + // Use the currency defaults if the values weren't set by the caller. + $resetMinimumFractionDigits = $resetMaximumFractionDigits = false; + if (!isset($this->minimumFractionDigits)) { + $this->minimumFractionDigits = $currency->getFractionDigits(); + $resetMinimumFractionDigits = true; + } + if (!isset($this->maximumFractionDigits)) { + $this->maximumFractionDigits = $currency->getFractionDigits(); + $resetMaximumFractionDigits = true; + } + + // Format the decimal part of the value first. + $value = $this->format($value); + + // Reset the fraction digit settings, so that they don't affect + // future formattings with different currencies. + if ($resetMinimumFractionDigits) { + $this->minimumFractionDigits = null; + } + if ($resetMaximumFractionDigits) { + $this->maximumFractionDigits = null; + } + + // Determine whether to show the currency symbol or the currency code. + if ($this->currencyDisplay == self::CURRENCY_DISPLAY_SYMBOL) { + $symbol = $currency->getSymbol(); + } else { + $symbol = $currency->getCurrencyCode(); + } + + return str_replace('¤', $symbol, $value); + } + + /** + * {@inheritdoc} + */ + public function parseCurrency($value, CurrencyInterface $currency) + { + $replacements = array( + // Convert the localized symbols back to their original form. + $this->numberFormat->getDecimalSeparator() => '.', + $this->numberFormat->getPlusSign() => '+', + $this->numberFormat->getMinusSign() => '-', + + // Strip any grouping separators, the currency code or symbol. + $this->numberFormat->getGroupingSeparator() => '', + $currency->getCurrencyCode() => '', + $currency->getSymbol() => '', + + // Strip whitespace (spaces and non-breaking spaces). + ' ' => '', + chr(0xC2) . chr(0xA0) => '', + ); + $numberingSystem = $this->numberFormat->getNumberingSystem(); + if (isset($this->digits[$numberingSystem])) { + // Convert the localized digits back to latin. + $replacements += array_flip($this->digits[$numberingSystem]); + } + + $value = strtr($value, $replacements); + if (substr($value, 0, 1) == '(' && substr($value, -1, 1) == ')') { + // This is an accounting formatted negative number. + $value = '-' . str_replace(array('(', ')'), '', $value); + } + + return is_numeric($value) ? $value : false; + } + + /** + * Replaces digits with their localized equivalents. + * + * @param string $value The value being formatted. + * + * @return string + */ + protected function replaceDigits($value) + { + $numberingSystem = $this->numberFormat->getNumberingSystem(); + if (isset($this->digits[$numberingSystem])) { + $value = strtr($value, $this->digits[$numberingSystem]); + } + + return $value; + } + + /** + * Replaces number symbols with their localized equivalents. + * + * @param string $value The value being formatted. + * + * @return string + * + * @see http://cldr.unicode.org/translation/number-symbols + */ + protected function replaceSymbols($value) + { + $replacements = array( + '.' => $this->numberFormat->getDecimalSeparator(), + ',' => $this->numberFormat->getGroupingSeparator(), + '+' => $this->numberFormat->getPlusSign(), + '-' => $this->numberFormat->getMinusSign(), + '%' => $this->numberFormat->getPercentSign(), + ); + + return strtr($value, $replacements); + } + + /** + * {@inheritdoc} + */ + public function getNumberFormat() + { + return $this->numberFormat; + } + + /** + * {@inheritdoc} + */ + public function getMinimumFractionDigits() + { + return $this->minimumFractionDigits; + } + + /** + * {@inheritdoc} + */ + public function setMinimumFractionDigits($minimumFractionDigits) + { + $this->minimumFractionDigits = $minimumFractionDigits; + + return $this; + } + + /** + * {@inheritdoc} + */ + public function getMaximumFractionDigits() + { + return $this->maximumFractionDigits; + } + + /** + * {@inheritdoc} + */ + public function setMaximumFractionDigits($maximumFractionDigits) + { + $this->maximumFractionDigits = $maximumFractionDigits; + + return $this; + } + + /** + * {@inheritdoc} + */ + public function isGroupingUsed() + { + return $this->groupingUsed; + } + + /** + * {@inheritdoc} + */ + public function setGroupingUsed($groupingUsed) + { + $this->groupingUsed = $groupingUsed; + + return $this; + } + + /** + * {@inheritdoc} + */ + public function getCurrencyDisplay() + { + return $this->currencyDisplay; + } + + /** + * {@inheritdoc} + */ + public function setCurrencyDisplay($currencyDisplay) + { + $this->currencyDisplay = $currencyDisplay; + + return $this; + } +} diff --git a/library/intl/src/Formatter/NumberFormatterInterface.php b/library/intl/src/Formatter/NumberFormatterInterface.php new file mode 100644 index 000000000..721107555 --- /dev/null +++ b/library/intl/src/Formatter/NumberFormatterInterface.php @@ -0,0 +1,134 @@ + Date: Wed, 31 Dec 2014 10:43:19 +1100 Subject: Revert "Language names via intl library." --- library/intl/src/Formatter/NumberFormatter.php | 406 --------------------- .../src/Formatter/NumberFormatterInterface.php | 134 ------- 2 files changed, 540 deletions(-) delete mode 100644 library/intl/src/Formatter/NumberFormatter.php delete mode 100644 library/intl/src/Formatter/NumberFormatterInterface.php (limited to 'library/intl/src/Formatter') diff --git a/library/intl/src/Formatter/NumberFormatter.php b/library/intl/src/Formatter/NumberFormatter.php deleted file mode 100644 index 0b8e2ae59..000000000 --- a/library/intl/src/Formatter/NumberFormatter.php +++ /dev/null @@ -1,406 +0,0 @@ - array( - 0 => '٠', 1 => '١', 2 => '٢', 3 => '٣', 4 => '٤', - 5 => '٥', 6 => '٦', 7 => '٧', 8 => '٨', 9 => '٩', - ), - NumberFormatInterface::NUMBERING_SYSTEM_ARABIC_EXTENDED => array( - 0 => '۰', 1 => '۱', 2 => '۲', 3 => '۳', 4 => '۴', - 5 => '۵', 6 => '۶', 7 => '۷', 8 => '۸', 9 => '۹', - ), - NumberFormatInterface::NUMBERING_SYSTEM_BENGALI => array( - 0 => '০', 1 => '১', 2 => '২', 3 => '৩', 4 => '৪', - 5 => '৫', 6 => '৬', 7 => '৭', 8 => '৮', 9 => '৯', - ), - NumberFormatInterface::NUMBERING_SYSTEM_DEVANAGARI => array( - 0 => '०', 1 => '१', 2 => '२', 3 => '३', 4 => '४', - 5 => '५', 6 => '६', 7 => '७', 8 => '८', 9 => '९', - ), - ); - - /** - * Creaes a NumberFormatter instance. - * - * @param NumberFormatInterface $numberFormat The number format. - * @param int $style The formatting style. - * - * @throws InvalidArgumentException - */ - public function __construct(NumberFormatInterface $numberFormat, $style = self::DECIMAL) - { - $availablePatterns = array( - self::DECIMAL => $numberFormat->getDecimalPattern(), - self::PERCENT => $numberFormat->getPercentPattern(), - self::CURRENCY => $numberFormat->getCurrencyPattern(), - self::CURRENCY_ACCOUNTING => $numberFormat->getAccountingCurrencyPattern(), - ); - if (!array_key_exists($style, $availablePatterns)) { - // Unknown type. - throw new InvalidArgumentException('Unknown format style provided to NumberFormatter::__construct().'); - } - - // Split the selected pattern into positive and negative patterns. - $patterns = explode(';', $availablePatterns[$style]); - if (!isset($patterns[1])) { - // No explicit negative pattern was provided, construct it. - $patterns[1] = '-' . $patterns[0]; - } - - $this->numberFormat = $numberFormat; - $this->positivePattern = $patterns[0]; - $this->negativePattern = $patterns[1]; - $this->groupingUsed = (strpos($this->positivePattern, ',') !== false); - // This pattern has number groups, parse them. - if ($this->groupingUsed) { - preg_match('/#+0/', $this->positivePattern, $primaryGroupMatches); - $this->primaryGroupSize = $this->secondaryGroupSize = strlen($primaryGroupMatches[0]); - $numberGroups = explode(',', $this->positivePattern); - if (count($numberGroups) > 2) { - // This pattern has a distinct secondary group size. - $this->secondaryGroupSize = strlen($numberGroups[1]); - } - } - - // Initialize the fraction digit settings for decimal and percent - // styles only. The currency ones will default to the currency values. - if (in_array($style, array(self::DECIMAL, self::PERCENT))) { - $this->minimumFractionDigits = 0; - $this->maximumFractionDigits = 3; - } - $this->currencyDisplay = self::CURRENCY_DISPLAY_SYMBOL; - } - - /** - * {@inheritdoc} - */ - public function format($value) - { - if (!is_numeric($value)) { - $message = sprintf('The provided value "%s" must be a valid number or numeric string.', $value); - throw new InvalidArgumentException($message); - } - - // Ensure that the value is positive and has the right number of digits. - $negative = (bccomp('0', $value, 12) == 1); - $signMultiplier = $negative ? '-1' : '1'; - $value = bcdiv($value, $signMultiplier, $this->maximumFractionDigits); - // Split the number into major and minor digits. - $valueParts = explode('.', $value); - $majorDigits = $valueParts[0]; - // Account for maximumFractionDigits = 0, where the number won't - // have a decimal point, and $valueParts[1] won't be set. - $minorDigits = isset($valueParts[1]) ? $valueParts[1] : ''; - - if ($this->groupingUsed) { - // Reverse the major digits, since they are grouped from the right. - $majorDigits = array_reverse(str_split($majorDigits)); - // Group the major digits. - $groups = array(); - $groups[] = array_splice($majorDigits, 0, $this->primaryGroupSize); - while (!empty($majorDigits)) { - $groups[] = array_splice($majorDigits, 0, $this->secondaryGroupSize); - } - // Reverse the groups and the digits inside of them. - $groups = array_reverse($groups); - foreach ($groups as &$group) { - $group = implode(array_reverse($group)); - } - // Reconstruct the major digits. - $majorDigits = implode(',', $groups); - } - - if ($this->minimumFractionDigits < $this->maximumFractionDigits) { - // Strip any trailing zeroes. - $minorDigits = rtrim($minorDigits, '0'); - if (strlen($minorDigits) < $this->minimumFractionDigits) { - // Now there are too few digits, re-add trailing zeroes - // until the desired length is reached. - $neededZeroes = $this->minimumFractionDigits - strlen($minorDigits); - $minorDigits .= str_repeat('0', $neededZeroes); - } - } - - // Assemble the final number and insert it into the pattern. - $value = $minorDigits ? $majorDigits . '.' . $minorDigits : $majorDigits; - $pattern = $negative ? $this->negativePattern : $this->positivePattern; - $value = preg_replace('/#(?:[\.,]#+)*0(?:[,\.][0#]+)*/', $value, $pattern); - - // Localize the number. - $value = $this->replaceDigits($value); - $value = $this->replaceSymbols($value); - - return $value; - } - - /** - * {@inheritdoc} - */ - public function formatCurrency($value, CurrencyInterface $currency) - { - // Use the currency defaults if the values weren't set by the caller. - $resetMinimumFractionDigits = $resetMaximumFractionDigits = false; - if (!isset($this->minimumFractionDigits)) { - $this->minimumFractionDigits = $currency->getFractionDigits(); - $resetMinimumFractionDigits = true; - } - if (!isset($this->maximumFractionDigits)) { - $this->maximumFractionDigits = $currency->getFractionDigits(); - $resetMaximumFractionDigits = true; - } - - // Format the decimal part of the value first. - $value = $this->format($value); - - // Reset the fraction digit settings, so that they don't affect - // future formattings with different currencies. - if ($resetMinimumFractionDigits) { - $this->minimumFractionDigits = null; - } - if ($resetMaximumFractionDigits) { - $this->maximumFractionDigits = null; - } - - // Determine whether to show the currency symbol or the currency code. - if ($this->currencyDisplay == self::CURRENCY_DISPLAY_SYMBOL) { - $symbol = $currency->getSymbol(); - } else { - $symbol = $currency->getCurrencyCode(); - } - - return str_replace('¤', $symbol, $value); - } - - /** - * {@inheritdoc} - */ - public function parseCurrency($value, CurrencyInterface $currency) - { - $replacements = array( - // Convert the localized symbols back to their original form. - $this->numberFormat->getDecimalSeparator() => '.', - $this->numberFormat->getPlusSign() => '+', - $this->numberFormat->getMinusSign() => '-', - - // Strip any grouping separators, the currency code or symbol. - $this->numberFormat->getGroupingSeparator() => '', - $currency->getCurrencyCode() => '', - $currency->getSymbol() => '', - - // Strip whitespace (spaces and non-breaking spaces). - ' ' => '', - chr(0xC2) . chr(0xA0) => '', - ); - $numberingSystem = $this->numberFormat->getNumberingSystem(); - if (isset($this->digits[$numberingSystem])) { - // Convert the localized digits back to latin. - $replacements += array_flip($this->digits[$numberingSystem]); - } - - $value = strtr($value, $replacements); - if (substr($value, 0, 1) == '(' && substr($value, -1, 1) == ')') { - // This is an accounting formatted negative number. - $value = '-' . str_replace(array('(', ')'), '', $value); - } - - return is_numeric($value) ? $value : false; - } - - /** - * Replaces digits with their localized equivalents. - * - * @param string $value The value being formatted. - * - * @return string - */ - protected function replaceDigits($value) - { - $numberingSystem = $this->numberFormat->getNumberingSystem(); - if (isset($this->digits[$numberingSystem])) { - $value = strtr($value, $this->digits[$numberingSystem]); - } - - return $value; - } - - /** - * Replaces number symbols with their localized equivalents. - * - * @param string $value The value being formatted. - * - * @return string - * - * @see http://cldr.unicode.org/translation/number-symbols - */ - protected function replaceSymbols($value) - { - $replacements = array( - '.' => $this->numberFormat->getDecimalSeparator(), - ',' => $this->numberFormat->getGroupingSeparator(), - '+' => $this->numberFormat->getPlusSign(), - '-' => $this->numberFormat->getMinusSign(), - '%' => $this->numberFormat->getPercentSign(), - ); - - return strtr($value, $replacements); - } - - /** - * {@inheritdoc} - */ - public function getNumberFormat() - { - return $this->numberFormat; - } - - /** - * {@inheritdoc} - */ - public function getMinimumFractionDigits() - { - return $this->minimumFractionDigits; - } - - /** - * {@inheritdoc} - */ - public function setMinimumFractionDigits($minimumFractionDigits) - { - $this->minimumFractionDigits = $minimumFractionDigits; - - return $this; - } - - /** - * {@inheritdoc} - */ - public function getMaximumFractionDigits() - { - return $this->maximumFractionDigits; - } - - /** - * {@inheritdoc} - */ - public function setMaximumFractionDigits($maximumFractionDigits) - { - $this->maximumFractionDigits = $maximumFractionDigits; - - return $this; - } - - /** - * {@inheritdoc} - */ - public function isGroupingUsed() - { - return $this->groupingUsed; - } - - /** - * {@inheritdoc} - */ - public function setGroupingUsed($groupingUsed) - { - $this->groupingUsed = $groupingUsed; - - return $this; - } - - /** - * {@inheritdoc} - */ - public function getCurrencyDisplay() - { - return $this->currencyDisplay; - } - - /** - * {@inheritdoc} - */ - public function setCurrencyDisplay($currencyDisplay) - { - $this->currencyDisplay = $currencyDisplay; - - return $this; - } -} diff --git a/library/intl/src/Formatter/NumberFormatterInterface.php b/library/intl/src/Formatter/NumberFormatterInterface.php deleted file mode 100644 index 721107555..000000000 --- a/library/intl/src/Formatter/NumberFormatterInterface.php +++ /dev/null @@ -1,134 +0,0 @@ - Date: Wed, 31 Dec 2014 10:42:08 +0100 Subject: Revert "Revert "Language names via intl library."" This reverts commit 4f35efa0bad4ae6489b63f3eebafe6542d654094. --- library/intl/src/Formatter/NumberFormatter.php | 406 +++++++++++++++++++++ .../src/Formatter/NumberFormatterInterface.php | 134 +++++++ 2 files changed, 540 insertions(+) create mode 100644 library/intl/src/Formatter/NumberFormatter.php create mode 100644 library/intl/src/Formatter/NumberFormatterInterface.php (limited to 'library/intl/src/Formatter') diff --git a/library/intl/src/Formatter/NumberFormatter.php b/library/intl/src/Formatter/NumberFormatter.php new file mode 100644 index 000000000..0b8e2ae59 --- /dev/null +++ b/library/intl/src/Formatter/NumberFormatter.php @@ -0,0 +1,406 @@ + array( + 0 => '٠', 1 => '١', 2 => '٢', 3 => '٣', 4 => '٤', + 5 => '٥', 6 => '٦', 7 => '٧', 8 => '٨', 9 => '٩', + ), + NumberFormatInterface::NUMBERING_SYSTEM_ARABIC_EXTENDED => array( + 0 => '۰', 1 => '۱', 2 => '۲', 3 => '۳', 4 => '۴', + 5 => '۵', 6 => '۶', 7 => '۷', 8 => '۸', 9 => '۹', + ), + NumberFormatInterface::NUMBERING_SYSTEM_BENGALI => array( + 0 => '০', 1 => '১', 2 => '২', 3 => '৩', 4 => '৪', + 5 => '৫', 6 => '৬', 7 => '৭', 8 => '৮', 9 => '৯', + ), + NumberFormatInterface::NUMBERING_SYSTEM_DEVANAGARI => array( + 0 => '०', 1 => '१', 2 => '२', 3 => '३', 4 => '४', + 5 => '५', 6 => '६', 7 => '७', 8 => '८', 9 => '९', + ), + ); + + /** + * Creaes a NumberFormatter instance. + * + * @param NumberFormatInterface $numberFormat The number format. + * @param int $style The formatting style. + * + * @throws InvalidArgumentException + */ + public function __construct(NumberFormatInterface $numberFormat, $style = self::DECIMAL) + { + $availablePatterns = array( + self::DECIMAL => $numberFormat->getDecimalPattern(), + self::PERCENT => $numberFormat->getPercentPattern(), + self::CURRENCY => $numberFormat->getCurrencyPattern(), + self::CURRENCY_ACCOUNTING => $numberFormat->getAccountingCurrencyPattern(), + ); + if (!array_key_exists($style, $availablePatterns)) { + // Unknown type. + throw new InvalidArgumentException('Unknown format style provided to NumberFormatter::__construct().'); + } + + // Split the selected pattern into positive and negative patterns. + $patterns = explode(';', $availablePatterns[$style]); + if (!isset($patterns[1])) { + // No explicit negative pattern was provided, construct it. + $patterns[1] = '-' . $patterns[0]; + } + + $this->numberFormat = $numberFormat; + $this->positivePattern = $patterns[0]; + $this->negativePattern = $patterns[1]; + $this->groupingUsed = (strpos($this->positivePattern, ',') !== false); + // This pattern has number groups, parse them. + if ($this->groupingUsed) { + preg_match('/#+0/', $this->positivePattern, $primaryGroupMatches); + $this->primaryGroupSize = $this->secondaryGroupSize = strlen($primaryGroupMatches[0]); + $numberGroups = explode(',', $this->positivePattern); + if (count($numberGroups) > 2) { + // This pattern has a distinct secondary group size. + $this->secondaryGroupSize = strlen($numberGroups[1]); + } + } + + // Initialize the fraction digit settings for decimal and percent + // styles only. The currency ones will default to the currency values. + if (in_array($style, array(self::DECIMAL, self::PERCENT))) { + $this->minimumFractionDigits = 0; + $this->maximumFractionDigits = 3; + } + $this->currencyDisplay = self::CURRENCY_DISPLAY_SYMBOL; + } + + /** + * {@inheritdoc} + */ + public function format($value) + { + if (!is_numeric($value)) { + $message = sprintf('The provided value "%s" must be a valid number or numeric string.', $value); + throw new InvalidArgumentException($message); + } + + // Ensure that the value is positive and has the right number of digits. + $negative = (bccomp('0', $value, 12) == 1); + $signMultiplier = $negative ? '-1' : '1'; + $value = bcdiv($value, $signMultiplier, $this->maximumFractionDigits); + // Split the number into major and minor digits. + $valueParts = explode('.', $value); + $majorDigits = $valueParts[0]; + // Account for maximumFractionDigits = 0, where the number won't + // have a decimal point, and $valueParts[1] won't be set. + $minorDigits = isset($valueParts[1]) ? $valueParts[1] : ''; + + if ($this->groupingUsed) { + // Reverse the major digits, since they are grouped from the right. + $majorDigits = array_reverse(str_split($majorDigits)); + // Group the major digits. + $groups = array(); + $groups[] = array_splice($majorDigits, 0, $this->primaryGroupSize); + while (!empty($majorDigits)) { + $groups[] = array_splice($majorDigits, 0, $this->secondaryGroupSize); + } + // Reverse the groups and the digits inside of them. + $groups = array_reverse($groups); + foreach ($groups as &$group) { + $group = implode(array_reverse($group)); + } + // Reconstruct the major digits. + $majorDigits = implode(',', $groups); + } + + if ($this->minimumFractionDigits < $this->maximumFractionDigits) { + // Strip any trailing zeroes. + $minorDigits = rtrim($minorDigits, '0'); + if (strlen($minorDigits) < $this->minimumFractionDigits) { + // Now there are too few digits, re-add trailing zeroes + // until the desired length is reached. + $neededZeroes = $this->minimumFractionDigits - strlen($minorDigits); + $minorDigits .= str_repeat('0', $neededZeroes); + } + } + + // Assemble the final number and insert it into the pattern. + $value = $minorDigits ? $majorDigits . '.' . $minorDigits : $majorDigits; + $pattern = $negative ? $this->negativePattern : $this->positivePattern; + $value = preg_replace('/#(?:[\.,]#+)*0(?:[,\.][0#]+)*/', $value, $pattern); + + // Localize the number. + $value = $this->replaceDigits($value); + $value = $this->replaceSymbols($value); + + return $value; + } + + /** + * {@inheritdoc} + */ + public function formatCurrency($value, CurrencyInterface $currency) + { + // Use the currency defaults if the values weren't set by the caller. + $resetMinimumFractionDigits = $resetMaximumFractionDigits = false; + if (!isset($this->minimumFractionDigits)) { + $this->minimumFractionDigits = $currency->getFractionDigits(); + $resetMinimumFractionDigits = true; + } + if (!isset($this->maximumFractionDigits)) { + $this->maximumFractionDigits = $currency->getFractionDigits(); + $resetMaximumFractionDigits = true; + } + + // Format the decimal part of the value first. + $value = $this->format($value); + + // Reset the fraction digit settings, so that they don't affect + // future formattings with different currencies. + if ($resetMinimumFractionDigits) { + $this->minimumFractionDigits = null; + } + if ($resetMaximumFractionDigits) { + $this->maximumFractionDigits = null; + } + + // Determine whether to show the currency symbol or the currency code. + if ($this->currencyDisplay == self::CURRENCY_DISPLAY_SYMBOL) { + $symbol = $currency->getSymbol(); + } else { + $symbol = $currency->getCurrencyCode(); + } + + return str_replace('¤', $symbol, $value); + } + + /** + * {@inheritdoc} + */ + public function parseCurrency($value, CurrencyInterface $currency) + { + $replacements = array( + // Convert the localized symbols back to their original form. + $this->numberFormat->getDecimalSeparator() => '.', + $this->numberFormat->getPlusSign() => '+', + $this->numberFormat->getMinusSign() => '-', + + // Strip any grouping separators, the currency code or symbol. + $this->numberFormat->getGroupingSeparator() => '', + $currency->getCurrencyCode() => '', + $currency->getSymbol() => '', + + // Strip whitespace (spaces and non-breaking spaces). + ' ' => '', + chr(0xC2) . chr(0xA0) => '', + ); + $numberingSystem = $this->numberFormat->getNumberingSystem(); + if (isset($this->digits[$numberingSystem])) { + // Convert the localized digits back to latin. + $replacements += array_flip($this->digits[$numberingSystem]); + } + + $value = strtr($value, $replacements); + if (substr($value, 0, 1) == '(' && substr($value, -1, 1) == ')') { + // This is an accounting formatted negative number. + $value = '-' . str_replace(array('(', ')'), '', $value); + } + + return is_numeric($value) ? $value : false; + } + + /** + * Replaces digits with their localized equivalents. + * + * @param string $value The value being formatted. + * + * @return string + */ + protected function replaceDigits($value) + { + $numberingSystem = $this->numberFormat->getNumberingSystem(); + if (isset($this->digits[$numberingSystem])) { + $value = strtr($value, $this->digits[$numberingSystem]); + } + + return $value; + } + + /** + * Replaces number symbols with their localized equivalents. + * + * @param string $value The value being formatted. + * + * @return string + * + * @see http://cldr.unicode.org/translation/number-symbols + */ + protected function replaceSymbols($value) + { + $replacements = array( + '.' => $this->numberFormat->getDecimalSeparator(), + ',' => $this->numberFormat->getGroupingSeparator(), + '+' => $this->numberFormat->getPlusSign(), + '-' => $this->numberFormat->getMinusSign(), + '%' => $this->numberFormat->getPercentSign(), + ); + + return strtr($value, $replacements); + } + + /** + * {@inheritdoc} + */ + public function getNumberFormat() + { + return $this->numberFormat; + } + + /** + * {@inheritdoc} + */ + public function getMinimumFractionDigits() + { + return $this->minimumFractionDigits; + } + + /** + * {@inheritdoc} + */ + public function setMinimumFractionDigits($minimumFractionDigits) + { + $this->minimumFractionDigits = $minimumFractionDigits; + + return $this; + } + + /** + * {@inheritdoc} + */ + public function getMaximumFractionDigits() + { + return $this->maximumFractionDigits; + } + + /** + * {@inheritdoc} + */ + public function setMaximumFractionDigits($maximumFractionDigits) + { + $this->maximumFractionDigits = $maximumFractionDigits; + + return $this; + } + + /** + * {@inheritdoc} + */ + public function isGroupingUsed() + { + return $this->groupingUsed; + } + + /** + * {@inheritdoc} + */ + public function setGroupingUsed($groupingUsed) + { + $this->groupingUsed = $groupingUsed; + + return $this; + } + + /** + * {@inheritdoc} + */ + public function getCurrencyDisplay() + { + return $this->currencyDisplay; + } + + /** + * {@inheritdoc} + */ + public function setCurrencyDisplay($currencyDisplay) + { + $this->currencyDisplay = $currencyDisplay; + + return $this; + } +} diff --git a/library/intl/src/Formatter/NumberFormatterInterface.php b/library/intl/src/Formatter/NumberFormatterInterface.php new file mode 100644 index 000000000..721107555 --- /dev/null +++ b/library/intl/src/Formatter/NumberFormatterInterface.php @@ -0,0 +1,134 @@ +