diff --git a/library/Zend/Feed/Writer/Extension/ITunes/Entry.php b/library/Zend/Feed/Writer/Extension/ITunes/Entry.php index 5cd2a7f4ee0..d4d90f53273 100644 --- a/library/Zend/Feed/Writer/Extension/ITunes/Entry.php +++ b/library/Zend/Feed/Writer/Extension/ITunes/Entry.php @@ -12,6 +12,8 @@ use Zend\Feed\Writer; use Zend\Feed\Writer\Extension; +use Zend\Stdlib\StringUtils; +use Zend\Stdlib\StringWrapper\StringWrapperInterface; /** * @category Zend @@ -33,6 +35,18 @@ class Entry */ protected $encoding = 'UTF-8'; + /** + * The used string wrapper supporting encoding + * + * @var StringWrapperInterface + */ + protected $stringWrapper; + + public function __construct() + { + $this->stringWrapper = StringUtils::getWrapper($this->encoding); + } + /** * Set feed encoding * @@ -41,7 +55,8 @@ class Entry */ public function setEncoding($enc) { - $this->encoding = $enc; + $this->stringWrapper = StringUtils::getWrapper($enc); + $this->encoding = $enc; return $this; } @@ -68,7 +83,8 @@ public function setItunesBlock($value) throw new Writer\Exception\InvalidArgumentException('invalid parameter: "block" may only' . ' contain alphabetic characters'); } - if (iconv_strlen($value, $this->getEncoding()) > 255) { + + if ($this->stringWrapper->strlen($value) > 255) { throw new Writer\Exception\InvalidArgumentException('invalid parameter: "block" may only' . ' contain a maximum of 255 characters'); } @@ -98,7 +114,7 @@ public function addItunesAuthors(array $values) */ public function addItunesAuthor($value) { - if (iconv_strlen($value, $this->getEncoding()) > 255) { + if ($this->stringWrapper->strlen($value) > 255) { throw new Writer\Exception\InvalidArgumentException('invalid parameter: any "author" may only' . ' contain a maximum of 255 characters each'); } @@ -160,8 +176,9 @@ public function setItunesKeywords(array $value) throw new Writer\Exception\InvalidArgumentException('invalid parameter: "keywords" may only' . ' contain a maximum of 12 terms'); } + $concat = implode(',', $value); - if (iconv_strlen($concat, $this->getEncoding()) > 255) { + if ($this->stringWrapper->strlen($concat) > 255) { throw new Writer\Exception\InvalidArgumentException('invalid parameter: "keywords" may only' . ' have a concatenated length of 255 chars where terms are delimited' . ' by a comma'); @@ -179,7 +196,7 @@ public function setItunesKeywords(array $value) */ public function setItunesSubtitle($value) { - if (iconv_strlen($value, $this->getEncoding()) > 255) { + if ($this->stringWrapper->strlen($value) > 255) { throw new Writer\Exception\InvalidArgumentException('invalid parameter: "subtitle" may only' . ' contain a maximum of 255 characters'); } @@ -196,7 +213,7 @@ public function setItunesSubtitle($value) */ public function setItunesSummary($value) { - if (iconv_strlen($value, $this->getEncoding()) > 4000) { + if ($this->stringWrapper->strlen($value) > 4000) { throw new Writer\Exception\InvalidArgumentException('invalid parameter: "summary" may only' . ' contain a maximum of 4000 characters'); } diff --git a/library/Zend/Feed/Writer/Extension/ITunes/Feed.php b/library/Zend/Feed/Writer/Extension/ITunes/Feed.php index 764d6c31068..b06622580b2 100644 --- a/library/Zend/Feed/Writer/Extension/ITunes/Feed.php +++ b/library/Zend/Feed/Writer/Extension/ITunes/Feed.php @@ -12,6 +12,8 @@ use Zend\Feed\Writer; use Zend\Uri; +use Zend\Stdlib\StringUtils; +use Zend\Stdlib\StringWrapper\StringWrapperInterface; /** * @category Zend @@ -33,6 +35,21 @@ class Feed */ protected $encoding = 'UTF-8'; + /** + * The used string wrapper supporting encoding + * + * @var StringWrapperInterface + */ + protected $stringWrapper; + + /** + * Constructor + */ + public function __construct() + { + $this->stringWrapper = StringUtils::getWrapper($this->encoding); + } + /** * Set feed encoding * @@ -41,7 +58,8 @@ class Feed */ public function setEncoding($enc) { - $this->encoding = $enc; + $this->stringWrapper = StringUtils::getWrapper($enc); + $this->encoding = $enc; return $this; } @@ -68,7 +86,7 @@ public function setItunesBlock($value) throw new Writer\Exception\InvalidArgumentException('invalid parameter: "block" may only' . ' contain alphabetic characters'); } - if (iconv_strlen($value, $this->getEncoding()) > 255) { + if ($this->stringWrapper->strlen($value) > 255) { throw new Writer\Exception\InvalidArgumentException('invalid parameter: "block" may only' . ' contain a maximum of 255 characters'); } @@ -99,7 +117,7 @@ public function addItunesAuthors(array $values) */ public function addItunesAuthor($value) { - if (iconv_strlen($value, $this->getEncoding()) > 255) { + if ($this->stringWrapper->strlen($value) > 255) { throw new Writer\Exception\InvalidArgumentException('invalid parameter: any "author" may only' . ' contain a maximum of 255 characters each'); } @@ -124,19 +142,19 @@ public function setItunesCategories(array $values) } foreach ($values as $key=>$value) { if (!is_array($value)) { - if (iconv_strlen($value, $this->getEncoding()) > 255) { + if ($this->stringWrapper->strlen($value) > 255) { throw new Writer\Exception\InvalidArgumentException('invalid parameter: any "category" may only' . ' contain a maximum of 255 characters each'); } $this->data['categories'][] = $value; } else { - if (iconv_strlen($key, $this->getEncoding()) > 255) { + if ($this->stringWrapper->strlen($key) > 255) { throw new Writer\Exception\InvalidArgumentException('invalid parameter: any "category" may only' . ' contain a maximum of 255 characters each'); } $this->data['categories'][$key] = array(); foreach ($value as $val) { - if (iconv_strlen($val, $this->getEncoding()) > 255) { + if ($this->stringWrapper->strlen($val) > 255) { throw new Writer\Exception\InvalidArgumentException('invalid parameter: any "category" may only' . ' contain a maximum of 255 characters each'); } @@ -221,7 +239,7 @@ public function setItunesKeywords(array $value) . ' contain a maximum of 12 terms'); } $concat = implode(',', $value); - if (iconv_strlen($concat, $this->getEncoding()) > 255) { + if ($this->stringWrapper->strlen($concat) > 255) { throw new Writer\Exception\InvalidArgumentException('invalid parameter: "keywords" may only' . ' have a concatenated length of 255 chars where terms are delimited' . ' by a comma'); @@ -274,8 +292,8 @@ public function addItunesOwner(array $value) throw new Writer\Exception\InvalidArgumentException('invalid parameter: any "owner" must' . ' be an array containing keys "name" and "email"'); } - if (iconv_strlen($value['name'], $this->getEncoding()) > 255 - || iconv_strlen($value['email'], $this->getEncoding()) > 255 + if ($this->stringWrapper->strlen($value['name']) > 255 + || $this->stringWrapper->strlen($value['email']) > 255 ) { throw new Writer\Exception\InvalidArgumentException('invalid parameter: any "owner" may only' . ' contain a maximum of 255 characters each for "name" and "email"'); @@ -296,7 +314,7 @@ public function addItunesOwner(array $value) */ public function setItunesSubtitle($value) { - if (iconv_strlen($value, $this->getEncoding()) > 255) { + if ($this->stringWrapper->strlen($value) > 255) { throw new Writer\Exception\InvalidArgumentException('invalid parameter: "subtitle" may only' . ' contain a maximum of 255 characters'); } @@ -313,7 +331,7 @@ public function setItunesSubtitle($value) */ public function setItunesSummary($value) { - if (iconv_strlen($value, $this->getEncoding()) > 4000) { + if ($this->stringWrapper->strlen($value) > 4000) { throw new Writer\Exception\InvalidArgumentException('invalid parameter: "summary" may only' . ' contain a maximum of 4000 characters'); } diff --git a/library/Zend/Mvc/View/Console/RouteNotFoundStrategy.php b/library/Zend/Mvc/View/Console/RouteNotFoundStrategy.php index 430ae095959..b0033ea371c 100644 --- a/library/Zend/Mvc/View/Console/RouteNotFoundStrategy.php +++ b/library/Zend/Mvc/View/Console/RouteNotFoundStrategy.php @@ -25,6 +25,7 @@ use Zend\ServiceManager\Exception\ServiceNotFoundException; use Zend\ServiceManager\ServiceManager; use Zend\Stdlib\ResponseInterface as Response; +use Zend\Stdlib\StringUtils; use Zend\Text\Table; use Zend\Version\Version; use Zend\View\Model\ConsoleModel; @@ -393,6 +394,7 @@ protected function renderTable($data, $cols, $consoleWidth) $result = ''; $padding = 2; + // If there is only 1 column, just concatenate it if ($cols == 1) { foreach ($data as $row) { @@ -401,12 +403,15 @@ protected function renderTable($data, $cols, $consoleWidth) return $result; } + // Get the string wrapper supporting UTF-8 character encoding + $strWrapper = StringUtils::getWrapper('UTF-8'); + // Determine max width for each column $maxW = array(); for ($x = 1; $x <= $cols; $x += 1) { $maxW[$x] = 0; foreach ($data as $row) { - $maxW[$x] = max($maxW[$x], mb_strlen($row[$x-1],'utf-8') + $padding * 2); + $maxW[$x] = max($maxW[$x], $strWrapper->strlen($row[$x-1]) + $padding * 2); } } diff --git a/library/Zend/ProgressBar/Adapter/Console.php b/library/Zend/ProgressBar/Adapter/Console.php index 6884c266f14..15eba4b0610 100644 --- a/library/Zend/ProgressBar/Adapter/Console.php +++ b/library/Zend/ProgressBar/Adapter/Console.php @@ -12,6 +12,7 @@ use Zend\ProgressBar\Adapter\Exception; use Zend\Stdlib\ErrorHandler; +use Zend\Stdlib\StringUtils; /** * Zend_ProgressBar_Adapter_Console offers a text-based progressbar for console @@ -438,7 +439,12 @@ public function notify($current, $max, $percent, $timeTaken, $timeRemaining, $te break; case self::ELEMENT_TEXT: - $renderedElements[] = \Zend\Text\MultiByte::strPad(substr($text, 0, $this->textWidth), $this->textWidth, ' ', STR_PAD_RIGHT, $this->charset); + $renderedElements[] = StringUtils::getWrapper($this->charset)->strPad( + substr($text, 0, $this->textWidth), + $this->textWidth, + ' ', + STR_PAD_RIGHT + ); break; } } diff --git a/library/Zend/Stdlib/Exception/ExtensionNotLoadedException.php b/library/Zend/Stdlib/Exception/ExtensionNotLoadedException.php new file mode 100644 index 00000000000..2270b5e00b8 --- /dev/null +++ b/library/Zend/Stdlib/Exception/ExtensionNotLoadedException.php @@ -0,0 +1,22 @@ +setEncoding($encoding, $convertEncoding); + return $wrapper; + } + } + + throw new Exception\RuntimeException( + 'No wrapper found supporting "' . $encoding . '"' + . (($convertEncoding !== null) ? ' and "' . $convertEncoding . '"' : '') + ); + } + + /** + * Get a list of all known single-byte character encodings + * + * @return string[] + */ + public static function getSingleByteEncodings() + { + return static::$singleByteEncodings; + } + + /** + * Check if a given encoding is a known single-byte character encoding + * + * @param string $encoding + * @return boolean + */ + public static function isSingleByteEncoding($encoding) + { + return in_array(strtoupper($encoding), static::$singleByteEncodings); + } + + /** + * Check if a given string is valid UTF-8 encoded + * + * @param string $str + * @return boolean + */ + public static function isValidUtf8($str) + { + return is_string($str) && ($str === '' || preg_match('/^./su', $str) == 1); + } +} diff --git a/library/Zend/Stdlib/StringWrapper/AbstractStringWrapper.php b/library/Zend/Stdlib/StringWrapper/AbstractStringWrapper.php new file mode 100644 index 00000000000..933482c73fa --- /dev/null +++ b/library/Zend/Stdlib/StringWrapper/AbstractStringWrapper.php @@ -0,0 +1,277 @@ +convertEncoding = $convertEncodingUpper; + } else { + $this->convertEncoding = null; + } + $this->encoding = $encodingUpper; + + return $this; + } + + /** + * Get the defined character encoding to work with + * + * @return string + * @throws Exception\LogicException If no encoding was defined + */ + public function getEncoding() + { + return $this->encoding; + } + + /** + * Get the defined character encoding to convert to + * + * @return string|null + */ + public function getConvertEncoding() + { + return $this->convertEncoding; + } + + /** + * Convert a string from defined character encoding to the defined convert encoding + * + * @param string $str + * @param boolean $reverse + * @return string|false + */ + public function convert($str, $reverse = false) + { + $encoding = $this->getEncoding(); + $convertEncoding = $this->getConvertEncoding(); + if ($convertEncoding === null) { + throw new Exception\LogicException( + 'No convert encoding defined' + ); + } + + if ($encoding === $convertEncoding) { + return $str; + } + + $from = $reverse ? $convertEncoding : $encoding; + $to = $reverse ? $encoding : $convertEncoding; + throw new Exception\RuntimeException(sprintf( + 'Converting from "%s" to "%s" isn\'t supported by this string wrapper', + $from, + $to + )); + } + + /** + * Wraps a string to a given number of characters + * + * @param string $str + * @param integer $width + * @param string $break + * @param boolean $cut + * @return string|false + */ + public function wordWrap($string, $width = 75, $break = "\n", $cut = false) + { + $string = (string) $string; + if ($string === '') { + return ''; + } + + $break = (string) $break; + if ($break === '') { + throw new Exception\InvalidArgumentException('Break string cannot be empty'); + } + + $width = (int) $width; + if ($width === 0 && $cut) { + throw new Exception\InvalidArgumentException('Cannot force cut when width is zero'); + } + + if (StringUtils::isSingleByteEncoding($this->getEncoding())) { + return wordwrap($string, $width, $break, $cut); + } + + $stringWidth = $this->strlen($string); + $breakWidth = $this->strlen($break); + + $result = ''; + $lastStart = $lastSpace = 0; + + for ($current = 0; $current < $stringWidth; $current++) { + $char = $this->substr($string, $current, 1); + + $possibleBreak = $char; + if ($breakWidth !== 1) { + $possibleBreak = $this->substr($string, $current, $breakWidth); + } + + if ($possibleBreak === $break) { + $result .= $this->substr($string, $lastStart, $current - $lastStart + $breakWidth); + $current += $breakWidth - 1; + $lastStart = $lastSpace = $current + 1; + continue; + } + + if ($char === ' ') { + if ($current - $lastStart >= $width) { + $result .= $this->substr($string, $lastStart, $current - $lastStart) . $break; + $lastStart = $current + 1; + } + + $lastSpace = $current; + continue; + } + + if ($current - $lastStart >= $width && $cut && $lastStart >= $lastSpace) { + $result .= $this->substr($string, $lastStart, $current - $lastStart) . $break; + $lastStart = $lastSpace = $current; + continue; + } + + if ($current - $lastStart >= $width && $lastStart < $lastSpace) { + $result .= $this->substr($string, $lastStart, $lastSpace - $lastStart) . $break; + $lastStart = $lastSpace = $lastSpace + 1; + continue; + } + } + + if ($lastStart !== $current) { + $result .= $this->substr($string, $lastStart, $current - $lastStart); + } + + return $result; + } + + /** + * Pad a string to a certain length with another string + * + * @param string $input + * @param integer $padLength + * @param string $padString + * @param integer $padType + * @return string + */ + public function strPad($input, $padLength, $padString = ' ', $padType = STR_PAD_RIGHT) + { + if (StringUtils::isSingleByteEncoding($this->getEncoding())) { + return str_pad($input, $padLength, $padString, $padType); + } + + $lengthOfPadding = $padLength - $this->strlen($input); + if ($lengthOfPadding <= 0) { + return $input; + } + + $padStringLength = $this->strlen($padString); + if ($padStringLength === 0) { + return $input; + } + + $repeatCount = floor($lengthOfPadding / $padStringLength); + + if ($padType === STR_PAD_BOTH) { + $lastStringLeft = ''; + $lastStringRight = ''; + $repeatCountLeft = $repeatCountRight = ($repeatCount - $repeatCount % 2) / 2; + + $lastStringLength = $lengthOfPadding - 2 * $repeatCountLeft * $padStringLength; + $lastStringLeftLength = $lastStringRightLength = floor($lastStringLength / 2); + $lastStringRightLength += $lastStringLength % 2; + + $lastStringLeft = $this->substr($padString, 0, $lastStringLeftLength); + $lastStringRight = $this->substr($padString, 0, $lastStringRightLength); + + return str_repeat($padString, $repeatCountLeft) . $lastStringLeft + . $input + . str_repeat($padString, $repeatCountRight) . $lastStringRight; + } + + $lastString = $this->substr($padString, 0, $lengthOfPadding % $padStringLength); + + if ($padType === STR_PAD_LEFT) { + return str_repeat($padString, $repeatCount) . $lastString . $input; + } + + return $input . str_repeat($padString, $repeatCount) . $lastString; + } +} diff --git a/library/Zend/Stdlib/StringWrapper/Iconv.php b/library/Zend/Stdlib/StringWrapper/Iconv.php new file mode 100644 index 00000000000..0cc8464eb67 --- /dev/null +++ b/library/Zend/Stdlib/StringWrapper/Iconv.php @@ -0,0 +1,298 @@ +getEncoding()); + } + + /** + * Returns the portion of string specified by the start and length parameters + * + * @param string $str + * @param int $offset + * @param int|null $length + * @param string $encoding + * @return string|false + */ + public function substr($str, $offset = 0, $length = null) + { + return iconv_substr($str, $offset, $length, $this->getEncoding()); + } + + /** + * Find the position of the first occurrence of a substring in a string + * + * @param string $haystack + * @param string $needle + * @param int $offset + * @param string $encoding + * @return int|false + */ + public function strpos($haystack, $needle, $offset = 0) + { + return iconv_strpos($haystack, $needle, $offset, $this->getEncoding()); + } + + /** + * Convert a string from defined encoding to the defined convert encoding + * + * @param string $str + * @param boolean $reverse + * @return string|false + */ + public function convert($str, $reverse = false) + { + $encoding = $this->getEncoding(); + $convertEncoding = $this->getConvertEncoding(); + if ($convertEncoding === null) { + throw new Exception\LogicException( + 'No convert encoding defined' + ); + } + + if ($encoding === $convertEncoding) { + return $str; + } + + $fromEncoding = $reverse ? $convertEncoding : $encoding; + $toEncoding = $reverse ? $encoding : $convertEncoding; + + // automatically add "//IGNORE" to not stop converting on invalid characters + // invalid characters triggers a notice anyway + return iconv($fromEncoding, $toEncoding . '//IGNORE', $str); + } +} diff --git a/library/Zend/Stdlib/StringWrapper/Intl.php b/library/Zend/Stdlib/StringWrapper/Intl.php new file mode 100644 index 00000000000..c7eacaa2404 --- /dev/null +++ b/library/Zend/Stdlib/StringWrapper/Intl.php @@ -0,0 +1,92 @@ +getEncoding()); + } + + /** + * Returns the portion of string specified by the start and length parameters + * + * @param string $str + * @param int $offset + * @param int|null $length + * @param string $encoding + * @return string|false + */ + public function substr($str, $offset = 0, $length = null) + { + return mb_substr($str, $offset, $length, $this->getEncoding()); + } + + /** + * Find the position of the first occurrence of a substring in a string + * + * @param string $haystack + * @param string $needle + * @param int $offset + * @param string $encoding + * @return int|false + */ + public function strpos($haystack, $needle, $offset = 0) + { + return mb_strpos($haystack, $needle, $offset, $this->getEncoding()); + } + + /** + * Convert a string from defined encoding to the defined convert encoding + * + * @param string $str + * @param boolean $reverse + * @return string|false + */ + public function convert($str, $reverse = false) + { + $encoding = $this->getEncoding(); + $convertEncoding = $this->getConvertEncoding(); + + if ($convertEncoding === null) { + throw new Exception\LogicException( + 'No convert encoding defined' + ); + } + + if ($encoding === $convertEncoding) { + return $str; + } + + $fromEncoding = $reverse ? $convertEncoding : $encoding; + $toEncoding = $reverse ? $encoding : $convertEncoding; + return mb_convert_encoding($str, $toEncoding, $fromEncoding); + } +} diff --git a/library/Zend/Stdlib/StringWrapper/Native.php b/library/Zend/Stdlib/StringWrapper/Native.php new file mode 100644 index 00000000000..375b46f2864 --- /dev/null +++ b/library/Zend/Stdlib/StringWrapper/Native.php @@ -0,0 +1,139 @@ +convertEncoding = $encodingUpper; + } + + if ($convertEncoding !== null) { + if ($encodingUpper !== strtoupper($convertEncoding)) { + throw new Exception\InvalidArgumentException( + 'Wrapper doesn\'t support to convert between character encodings' + ); + } + + $this->convertEncoding = $encodingUpper; + } else { + $this->convertEncoding = null; + } + $this->encoding = $encodingUpper; + + return $this; + } + + /** + * Returns the length of the given string + * + * @param string $str + * @return int|false + */ + public function strlen($str) + { + return strlen($str); + } + + /** + * Returns the portion of string specified by the start and length parameters + * + * @param string $str + * @param int $offset + * @param int|null $length + * @return string|false + */ + public function substr($str, $offset = 0, $length = null) + { + return substr($str, $offset, $length); + } + + /** + * Find the position of the first occurrence of a substring in a string + * + * @param string $haystack + * @param string $needle + * @param int $offset + * @return int|false + */ + public function strpos($haystack, $needle, $offset = 0) + { + return strpos($haystack, $needle, $offset); + } +} diff --git a/library/Zend/Stdlib/StringWrapper/StringWrapperInterface.php b/library/Zend/Stdlib/StringWrapper/StringWrapperInterface.php new file mode 100644 index 00000000000..bcba9ec48e5 --- /dev/null +++ b/library/Zend/Stdlib/StringWrapper/StringWrapperInterface.php @@ -0,0 +1,119 @@ +convert($text); + if (!StringUtils::isValidUtf8($text)) { + throw new Exception\UnexpectedValueException('$text is not encoded with ' . $encoding); } + $strWrapper = StringUtils::getWrapper('UTF-8'); + $this->output = ''; $this->outputLine = array(); @@ -427,21 +435,14 @@ public function render($text, $encoding = 'UTF-8') $wordBreakMode = 0; $lastCharWasEol = false; - - ErrorHandler::start(E_NOTICE); - $textLength = iconv_strlen($text, 'UTF-8'); - $error = ErrorHandler::stop(); - - if ($textLength === false) { - throw new Exception\UnexpectedValueException('$text is not encoded with ' . $encoding, 0, $error); - } + $textLength = $strWrapper->strlen($text); for ($charNum = 0; $charNum < $textLength; $charNum++) { // Handle paragraphs - $char = iconv_substr($text, $charNum, 1, 'UTF-8'); + $char = $strWrapper->substr($text, $charNum, 1); if ($char === "\n" && $this->handleParagraphs && !$lastCharWasEol) { - $nextChar = iconv_substr($text, ($charNum + 1), 1, 'UTF-8'); + $nextChar = $strWrapper->substr($text, ($charNum + 1), 1); if (!$nextChar) { $nextChar = null; } @@ -686,7 +687,7 @@ protected function _addChar($char) } } - $this->outlineLength = strlen($this->outputLine[0]); + $this->outlineLength = strlen($this->outputLine[0]); $this->inCharLine[$this->inCharLineLength++] = $char; return true; diff --git a/library/Zend/Text/MultiByte.php b/library/Zend/Text/MultiByte.php index b541004fd47..6e744d6f11b 100644 --- a/library/Zend/Text/MultiByte.php +++ b/library/Zend/Text/MultiByte.php @@ -10,6 +10,8 @@ namespace Zend\Text; +use Zend\Stdlib\StringUtils; + /** * Contains multibyte safe string methods * @@ -28,70 +30,20 @@ class MultiByte * @param string $charset * @throws Exception\InvalidArgumentException * @return string + * @deprecated Please use Zend\Stdlib\StringUtils instead */ public static function wordWrap($string, $width = 75, $break = "\n", $cut = false, $charset = 'utf-8') { - $stringWidth = iconv_strlen($string, $charset); - $breakWidth = iconv_strlen($break, $charset); - - if (strlen($string) === 0) { - return ''; - } - - if ($breakWidth === null) { - throw new Exception\InvalidArgumentException('Break string cannot be empty'); + trigger_error(sprintf( + "This method is deprecated, please use '%s' instead", + 'Zend\Stdlib\StringUtils::getWrapper()->wordWrap' + ), E_USER_DEPRECATED); + + try { + return StringUtils::getWrapper($charset)->wordWrap($string, $width, $break, $cut); + } catch (\Zend\Stdlib\Exception\InvalidArgumentException $e) { + throw new Exception\InvalidArgumentException($e->getMessage(), $e->getCode(), $e); } - - if ($width === 0 && $cut) { - throw new Exception\InvalidArgumentException('Cannot force cut when width is zero'); - } - - $result = ''; - $lastStart = $lastSpace = 0; - - for ($current = 0; $current < $stringWidth; $current++) { - $char = iconv_substr($string, $current, 1, $charset); - - $possibleBreak = $char; - if ($breakWidth !== 1) { - $possibleBreak = iconv_substr($string, $current, $breakWidth, $charset); - } - - if ($possibleBreak === $break) { - $result .= iconv_substr($string, $lastStart, $current - $lastStart + $breakWidth, $charset); - $current += $breakWidth - 1; - $lastStart = $lastSpace = $current + 1; - continue; - } - - if ($char === ' ') { - if ($current - $lastStart >= $width) { - $result .= iconv_substr($string, $lastStart, $current - $lastStart, $charset) . $break; - $lastStart = $current + 1; - } - - $lastSpace = $current; - continue; - } - - if ($current - $lastStart >= $width && $cut && $lastStart >= $lastSpace) { - $result .= iconv_substr($string, $lastStart, $current - $lastStart, $charset) . $break; - $lastStart = $lastSpace = $current; - continue; - } - - if ($current - $lastStart >= $width && $lastStart < $lastSpace) { - $result .= iconv_substr($string, $lastStart, $lastSpace - $lastStart, $charset) . $break; - $lastStart = $lastSpace = $lastSpace + 1; - continue; - } - } - - if ($lastStart !== $current) { - $result .= iconv_substr($string, $lastStart, $current - $lastStart, $charset); - } - - return $result; } /** @@ -103,44 +55,15 @@ public static function wordWrap($string, $width = 75, $break = "\n", $cut = fals * @param integer $padType * @param string $charset * @return string + * @deprecated Please use Zend\Stdlib\StringUtils instead */ public static function strPad($input, $padLength, $padString = ' ', $padType = STR_PAD_RIGHT, $charset = 'utf-8') { - $return = ''; - $lengthOfPadding = $padLength - iconv_strlen($input, $charset); - $padStringLength = iconv_strlen($padString, $charset); - - if ($padStringLength === 0 || $lengthOfPadding <= 0) { - $return = $input; - } else { - $repeatCount = floor($lengthOfPadding / $padStringLength); - - if ($padType === STR_PAD_BOTH) { - $lastStringLeft = ''; - $lastStringRight = ''; - $repeatCountLeft = $repeatCountRight = ($repeatCount - $repeatCount % 2) / 2; - - $lastStringLength = $lengthOfPadding - 2 * $repeatCountLeft * $padStringLength; - $lastStringLeftLength = $lastStringRightLength = floor($lastStringLength / 2); - $lastStringRightLength += $lastStringLength % 2; - - $lastStringLeft = iconv_substr($padString, 0, $lastStringLeftLength, $charset); - $lastStringRight = iconv_substr($padString, 0, $lastStringRightLength, $charset); - - $return = str_repeat($padString, $repeatCountLeft) . $lastStringLeft - . $input - . str_repeat($padString, $repeatCountRight) . $lastStringRight; - } else { - $lastString = iconv_substr($padString, 0, $lengthOfPadding % $padStringLength, $charset); - - if ($padType === STR_PAD_LEFT) { - $return = str_repeat($padString, $repeatCount) . $lastString . $input; - } else { - $return = $input . str_repeat($padString, $repeatCount) . $lastString; - } - } - } + trigger_error(sprintf( + "This method is deprecated, please use '%s' instead", + 'Zend\Stdlib\StringUtils::getWrapper()->strPad' + ), E_USER_DEPRECATED); - return $return; + return StringUtils::getWrapper($charset)->strPad($input, $padLength, $padString, $padType); } } diff --git a/library/Zend/Text/Table/Column.php b/library/Zend/Text/Table/Column.php index 4c794613786..35a997debd5 100644 --- a/library/Zend/Text/Table/Column.php +++ b/library/Zend/Text/Table/Column.php @@ -10,6 +10,7 @@ namespace Zend\Text\Table; +use Zend\Stdlib\StringUtils; use Zend\Text; /** @@ -107,7 +108,8 @@ public function setContent($content, $charset = null) if ($inputCharset !== $outputCharset) { if (PHP_OS !== 'AIX') { // AIX does not understand these character sets - $content = iconv($inputCharset, $outputCharset, $content); + $strWrapper = StringUtils::getWrapper($inputCharset, $outputCharset); + $content = $strWrapper->convert($content); } } @@ -203,12 +205,13 @@ public function render($columnWidth, $padding = 0) } $outputCharset = Table::getOutputCharset(); - $lines = explode("\n", Text\MultiByte::wordWrap($this->content, $columnWidth, "\n", true, $outputCharset)); + $strWrapper = StringUtils::getWrapper($outputCharset); + $lines = explode("\n", $strWrapper->wordWrap($this->content, $columnWidth, "\n", true)); $paddedLines = array(); - foreach ($lines AS $line) { + foreach ($lines as $line) { $paddedLines[] = str_repeat(' ', $padding) - . Text\MultiByte::strPad($line, $columnWidth, ' ', $padMode, $outputCharset) + . $strWrapper->strPad($line, $columnWidth, ' ', $padMode) . str_repeat(' ', $padding); } diff --git a/library/Zend/Validator/Barcode/Code128.php b/library/Zend/Validator/Barcode/Code128.php index 110713c6f63..640d39d4411 100644 --- a/library/Zend/Validator/Barcode/Code128.php +++ b/library/Zend/Validator/Barcode/Code128.php @@ -10,12 +10,23 @@ namespace Zend\Validator\Barcode; +use Zend\Validator\Exception; +use Zend\Stdlib\StringUtils; +use Zend\Stdlib\StringWrapper\StringWrapperInterface; + /** * @category Zend * @package Zend_Validator */ class Code128 extends AbstractAdapter { + /** + * The used string wrapper used for basic UTF-8 string functions + * + * @var SrtringWrapperInterface + */ + protected $utf8StringWrapper; + /** * Constructor for this barcode adapter */ @@ -72,6 +83,29 @@ public function __construct() } + public function setUtf8StringWrapper(StringWrapperInterface $utf8StringWrapper) + { + if (!$utf8StringWrapper->isSupported('UTF-8')) { + throw new Exception\InvalidArgumentException( + "The string wrapper needs to support UTF-8 character encoding" + ); + } + $this->utf8StringWrapper = $utf8StringWrapper; + } + + /** + * Get the string wrapper supporting UTF-8 character encoding + * + * @return StringWrapperInterface + */ + public function getUtf8StringWrapper() + { + if (!$this->utf8StringWrapper) { + $this->utf8StringWrapper = StringUtils::getWrapper('UTF-8'); + } + return $this->utf8StringWrapper; + } + /** * Checks for allowed characters within the barcode * @@ -84,16 +118,19 @@ public function hasValidCharacters($value) return false; } + // get used string wrapper for UTF-8 character encoding + $strWrapper = $this->getUtf8StringWrapper(); + // detect starting charset $set = $this->getCodingSet($value); $read = $set; if ($set != '') { - $value = iconv_substr($value, 1, iconv_strlen($value, 'UTF-8'), 'UTF-8'); + $value = $strWrapper->substr($value, 1, null); } // process barcode while ($value != '') { - $char = iconv_substr($value, 0, 1, 'UTF-8'); + $char = $strWrapper->substr($value, 0, 1); switch ($char) { // Function definition @@ -149,11 +186,11 @@ public function hasValidCharacters($value) break; } - $value = iconv_substr($value, 1); + $value = $strWrapper->substr($value, 1, null); $read = $set; } - if (($value != '') && (iconv_strlen($value, 'UTF-8') != 1)) { + if (($value != '') && ($strWrapper->strlen($value) != 1)) { return false; } @@ -173,7 +210,8 @@ protected function code128($value) $set = $this->getCodingSet($value); $read = $set; $usecheck = $this->useChecksum(null); - $char = iconv_substr($value, 0, 1, 'UTF-8'); + $strWrapper = $this->getUtf8StringWrapper(); + $char = $strWrapper->substr($value, 0, 1); if ($char == '‡') { $sum = 103; } elseif ($char == 'ˆ') { @@ -185,11 +223,11 @@ protected function code128($value) return false; } - $value = iconv_substr($value, 1, iconv_strlen($value, 'UTF-8'), 'UTF-8'); - while (iconv_strpos($value, 'Š') || ($value != '')) { - $char = iconv_substr($value, 0, 1, 'UTF-8'); + $value = $strWrapper->substr($value, 1, null); + while ($strWrapper->strpos($value, 'Š') || ($value != '')) { + $char = $strWrapper->substr($value, 0, 1); if ($read == 'C') { - $char = iconv_substr($value, 0, 2, 'UTF-8'); + $char = $strWrapper->substr($value, 0, 2); } switch ($char) { @@ -246,22 +284,22 @@ protected function code128($value) break; } - $value = iconv_substr($value, 1, iconv_strlen($value, 'UTF-8'), 'UTF-8'); + $value = $strWrapper->substr($value, 1); ++$pos; - if ((iconv_strpos($value, 'Š', 0, 'UTF-8') == 1) && (iconv_strlen($value, 'UTF-8') == 2)) { + if (($strWrapper->strpos($value, 'Š') == 1) && ($strWrapper->strlen($value) == 2)) { // break by stop and checksum char break; } $read = $set; } - if ((iconv_strpos($value, 'Š', 0, 'UTF-8') != 1) || (iconv_strlen($value, 'UTF-8') != 2)) { + if (($strWrapper->strpos($value, 'Š') != 1) || ($strWrapper->strlen($value) != 2)) { // return false if checksum is not readable and true if no startvalue is detected return (!$usecheck); } $mod = $sum % 103; - if (iconv_substr($value, 0, 1, 'UTF-8') == $this->chr128($mod, $set)) { + if ($strWrapper->substr($value, 0, 1) == $this->chr128($mod, $set)) { return true; } @@ -276,7 +314,7 @@ protected function code128($value) */ protected function getCodingSet($value) { - $value = iconv_substr($value, 0, 1, 'UTF-8'); + $value = $this->getUtf8StringWrapper()->substr($value, 0, 1); switch ($value) { case '‡' : return 'A'; diff --git a/library/Zend/Validator/Hostname.php b/library/Zend/Validator/Hostname.php index bc90c922685..d465adcdbac 100644 --- a/library/Zend/Validator/Hostname.php +++ b/library/Zend/Validator/Hostname.php @@ -11,6 +11,7 @@ namespace Zend\Validator; use Zend\Stdlib\ErrorHandler; +use Zend\Stdlib\StringUtils; /** * Please note there are two standalone test scripts for testing IDN characters due to problems @@ -485,10 +486,9 @@ public function isValid($value) // Check input against DNS hostname schema if ((count($domainParts) > 1) && (strlen($value) >= 4) && (strlen($value) <= 254)) { - $status = false; + $utf8StrWrapper = StringUtils::getWrapper('UTF-8'); + $status = false; - $origenc = iconv_get_encoding('internal_encoding'); - iconv_set_encoding('internal_encoding', 'UTF-8'); do { // First check TLD $matches = array(); @@ -558,7 +558,7 @@ public function isValid($value) $length = $this->idnLength[strtoupper($this->tld)]; } - if (iconv_strlen($domainPart, 'UTF-8') > $length) { + if ($utf8StrWrapper->strlen($domainPart) > $length) { $this->error(self::INVALID_HOSTNAME); } else { $checked = true; @@ -584,7 +584,6 @@ public function isValid($value) } } while (false); - iconv_set_encoding('internal_encoding', $origenc); // If the input passes as an Internet domain name, and domain names are allowed, then the hostname // passes validation if ($status && ($this->getAllow() & self::ALLOW_DNS)) { diff --git a/library/Zend/Validator/StringLength.php b/library/Zend/Validator/StringLength.php index 63abd217694..e7e9ad10244 100644 --- a/library/Zend/Validator/StringLength.php +++ b/library/Zend/Validator/StringLength.php @@ -10,6 +10,9 @@ namespace Zend\Validator; +use Zend\Stdlib\StringUtils; +use Zend\Stdlib\StringWrapper\StringWrapperInterface as StringWrapper; + /** * @category Zend * @package Zend_Validator @@ -38,11 +41,13 @@ class StringLength extends AbstractValidator ); protected $options = array( - 'min' => 0, // Minimum length - 'max' => null, // Maximum length, null if there is no length limitation - 'encoding' => null, // Encoding to use + 'min' => 0, // Minimum length + 'max' => null, // Maximum length, null if there is no length limitation + 'encoding' => 'UTF-8', // Encoding to use ); + protected $stringWrapper; + /** * Sets validator options * @@ -126,6 +131,31 @@ public function setMax($max) return $this; } + /** + * Get the string wrapper to detect the string length + * + * @return StringWrapper + */ + public function getStringWrapper() + { + if (!$this->stringWrapper) { + $this->stringWrapper = StringUtils::getWrapper($this->getEncoding()); + } + return $this->stringWrapper; + } + + /** + * Set the string wrapper to detect the string length + * + * @param StringWrapper + * @return StringLength + */ + public function setStringWrapper(StringWrapper $stringWrapper) + { + $stringWrapper->setEncoding($this->getEncoding()); + $this->stringWrapper = $stringWrapper; + } + /** * Returns the actual encoding * @@ -143,18 +173,9 @@ public function getEncoding() * @return StringLength * @throws Exception\InvalidArgumentException */ - public function setEncoding($encoding = null) + public function setEncoding($encoding) { - if ($encoding !== null) { - $orig = iconv_get_encoding('internal_encoding'); - $result = iconv_set_encoding('internal_encoding', $encoding); - if (!$result) { - throw new Exception\InvalidArgumentException('Given encoding not supported on this OS!'); - } - - iconv_set_encoding('internal_encoding', $orig); - } - + $this->stringWrapper = StringUtils::getWrapper($encoding); $this->options['encoding'] = $encoding; return $this; } @@ -174,12 +195,8 @@ public function isValid($value) } $this->setValue($value); - if ($this->getEncoding() !== null) { - $length = iconv_strlen($value, $this->getEncoding()); - } else { - $length = iconv_strlen($value); - } + $length = $this->getStringWrapper()->strlen($value); if ($length < $this->getMin()) { $this->error(self::TOO_SHORT); } diff --git a/tests/ZendTest/Stdlib/StringUtilsTest.php b/tests/ZendTest/Stdlib/StringUtilsTest.php new file mode 100644 index 00000000000..bb27a59bf5b --- /dev/null +++ b/tests/ZendTest/Stdlib/StringUtilsTest.php @@ -0,0 +1,150 @@ +assertTrue(StringUtils::isSingleByteEncoding($encoding)); + } + + public function getNonSingleByteEncodings() + { + return array( + array('UTf-8'), + array('UTf-16'), + array('usC-2'), + array('CESU-8'), + ); + } + + /** + * @dataProvider getNonSingleByteEncodings + * @param string $encoding + */ + public function testIsSingleByteEncodingReturnsFalse($encoding) + { + $this->assertFalse(StringUtils::isSingleByteEncoding($encoding)); + } + + public function testGetWrapper() + { + $wrapper = StringUtils::getWrapper('ISO-8859-1'); + if (extension_loaded('mbstring')) { + $this->assertInstanceOf('Zend\Stdlib\StringWrapper\MbString', $wrapper); + } elseif (extension_loaded('iconv')) { + $this->assertInstanceOf('Zend\Stdlib\StringWrapper\Iconv', $wrapper); + } else { + $this->assertInstanceOf('Zend\Stdlib\StringWrapper\Native', $wrapper); + } + + try { + $wrapper = StringUtils::getWrapper('UTF-8'); + if (extension_loaded('intl')) { + $this->assertInstanceOf('Zend\Stdlib\StringWrapper\Intl', $wrapper); + } elseif (extension_loaded('mbstring')) { + $this->assertInstanceOf('Zend\Stdlib\StringWrapper\MbString', $wrapper); + } elseif (extension_loaded('iconv')) { + $this->assertInstanceOf('Zend\Stdlib\StringWrapper\Iconv', $wrapper); + } + } catch (Exception $e) { + if (extension_loaded('intl') + || extension_loaded('mbstring') + || extension_loaded('iconv') + ) { + $this->fail("Failed to get intl, mbstring or iconv wrapper for UTF-8"); + } + } + + try { + $wrapper = StringUtils::getWrapper('UTF-8', 'ISO-8859-1'); + if (extension_loaded('mbstring')) { + $this->assertInstanceOf('Zend\Stdlib\StringWrapper\MbString', $wrapper); + } elseif (extension_loaded('iconv')) { + $this->assertInstanceOf('Zend\Stdlib\StringWrapper\Iconv', $wrapper); + } + } catch (Exception $e) { + if (extension_loaded('mbstring') || extension_loaded('iconv')) { + $this->fail("Failed to get mbstring or iconv wrapper for UTF-8 and ISO-8859-1"); + } + } + } + + public function getUtf8StringValidity() + { + return array( + // valid + array('', true), + array("\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0A\x0B\x0C\x0D\x0E\x0F" + . "\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1A\x1B\x1C\x1D\x1E\x1F" + . ' !"#$%&\'()*+,-./0123456789:;<=>?' + . '@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_' + . '`abcdefghijklmnopqrstuvwxyz{|}~', + true + ), + + // invalid + array(true, false), + array(123, false), + array(123.45, false), + array("\xFF", false), + array("\x90a", false), + ); + } + + /** + * @dataProvider getUtf8StringValidity + * @param string $str + * @param boolean $valid + */ + public function testIsValidUtf8($str, $valid) + { + $this->assertSame($valid, StringUtils::isValidUtf8($str)); + } +} diff --git a/tests/ZendTest/Stdlib/StringWrapper/CommonStringWrapperTest.php b/tests/ZendTest/Stdlib/StringWrapper/CommonStringWrapperTest.php new file mode 100644 index 00000000000..3640431d81b --- /dev/null +++ b/tests/ZendTest/Stdlib/StringWrapper/CommonStringWrapperTest.php @@ -0,0 +1,314 @@ +getWrapper($encoding); + if (!$wrapper) { + $this->markTestSkipped("Encoding {$encoding} not supported"); + } + + $result = $wrapper->strlen($str); + $this->assertSame($expected, $result); + } + + public function substrProvider() + { + return array( + array('ascii', 'abcdefghijkl', 1, 5, 'bcdef'), + array('utf-8', 'abcdefghijkl', 1, 5, 'bcdef'), + array('utf-8', 'äöüß', 1, 2, 'öü'), + ); + } + + /** + * @dataProvider substrProvider + * @param string $encoding + * @param string $str + * @param int $offset + * @param int|null $length + * @param mixed $expected + */ + public function testSubstr($encoding, $str, $offset, $length, $expected) + { + $wrapper = $this->getWrapper($encoding); + if (!$wrapper) { + $this->markTestSkipped("Encoding {$encoding} not supported"); + } + + $result = $wrapper->substr($str, $offset, $length); + $this->assertSame($expected, $result); + } + + public function strposProvider() + { + return array( + array('ascii', 'abcdefghijkl', 'g', 3, 6), + array('utf-8', 'abcdefghijkl', 'g', 3, 6), + array('utf-8', 'äöüß', 'ü', 1, 2), + ); + } + + /** + * @dataProvider strposProvider + * @param string $encoding + * @param string $haystack + * @param string $needle + * @param int $offset + * @param mixed $expected + */ + public function testStrpos($encoding, $haystack, $needle, $offset, $expected) + { + $wrapper = $this->getWrapper($encoding); + if (!$wrapper) { + $this->markTestSkipped("Encoding {$encoding} not supported"); + } + + $result = $wrapper->strpos($haystack, $needle, $offset); + $this->assertSame($expected, $result); + } + + public function convertProvider() + { + return array( + array('ascii', 'ascii', 'abc', 'abc'), + array('ascii', 'utf-8', 'abc', 'abc'), + array('utf-8', 'ascii', 'abc', 'abc'), + array('utf-8', 'iso-8859-15', '€', "\xA4"), + array('utf-8', 'iso-8859-16', '€', "\xA4"), // ISO-8859-16 is wrong @ mbstring + ); + } + + /** + * @dataProvider convertProvider + * @param string $str + * @param string $encoding + * @param string $convertEncoding + * @param mixed $expected + */ + public function testConvert($encoding, $convertEncoding, $str, $expected) + { + $wrapper = $this->getWrapper($encoding, $convertEncoding); + if (!$wrapper) { + $this->markTestSkipped("Encoding {$encoding} or {$convertEncoding} not supported"); + } + + $result = $wrapper->convert($str); + $this->assertSame($expected, $result); + + // backword + $result = $wrapper->convert($expected, true); + $this->assertSame($str, $result); + } + + public function testConvertDontSubstringsOnInvalidCharacter() + { + $wrapper = $this->getWrapper('UTF-8', 'ASCII'); + if (!$wrapper) { + $this->markTestSkipped("Converting UTF-8 to ASCII not supported"); + } + + // NOTE: This call could trigger a notice/warning/error + // but that isn't part of this test + ErrorHandler::start(E_NOTICE); + $result = $wrapper->convert($wrapper->convert("foo \xDEx bar"), true); + ErrorHandler::stop(); + + $this->assertSame("foo x bar", $result); + } + + public function wordWrapProvider() + { + return array( + // Standard cut tests + 'cut-single-line' => + array('utf-8', 'äbüöcß', 2, ' ', true, 'äb üö cß'), + 'cut-multi-line' => + array('utf-8', 'äbüöc ß äbüöcß', 2, ' ', true, 'äb üö c ß äb üö cß'), + 'cut-multi-line-short-words' => + array('utf-8', 'Ä very long wöööööööööööörd.', 8, "\n", true, + "Ä very\nlong\nwööööööö\nööööörd."), + 'cut-multi-line-with-previous-new-lines' => + array('utf-8', "Ä very\nlong wöööööööööööörd.", 8, "\n", false, + "Ä very\nlong\nwöööööööööööörd."), + 'long-break' => + array('utf-8', "Ä very
long wöö
öööööööö
öörd.", 8, '
', false, + "Ä very
long wöö
öööööööö
öörd."), + + // Alternative cut tests + 'cut-beginning-single-space' => + array('utf-8', ' äüöäöü', 3, ' ', true, ' äüö äöü'), + 'cut-ending-single-space' => + array('utf-8', 'äüöäöü ', 3, ' ', true, 'äüö äöü '), + 'cut-ending-single-space-with-non-space-divider' => + array('utf-8', 'äöüäöü ', 3, '-', true, 'äöü-äöü-'), + 'cut-ending-two-spaces' => + array('utf-8', 'äüöäöü ', 3, ' ', true, 'äüö äöü '), + 'no-cut-ending-single-space' => + array('utf-8', '12345 ', 5, '-', false, '12345-'), + 'no-cut-ending-two-spaces' => + array('utf-8', '12345 ', 5, '-', false, '12345- '), + 'cut-ending-three-spaces' => + array('utf-8', 'äüöäöü ', 3, ' ', true, 'äüö äöü '), + 'cut-ending-two-breaks' => + array('utf-8', 'äüöäöü--', 3, '-', true, 'äüö-äöü--'), + 'cut-tab' => + array('utf-8', "äbü\töcß", 3, ' ', true, "äbü \töc ß"), + 'cut-new-line-with-space' => + array('utf-8', "äbü\nößt", 3, ' ', true, "äbü \nöß t"), + 'cut-new-line-with-new-line' => + array('utf-8', "äbü\nößte", 3, "\n", true, "äbü\nößt\ne"), + + // Break cut tests + 'cut-break-before' => + array('ascii', 'foobar-foofoofoo', 8, '-', true, 'foobar-foofoofo-o'), + 'cut-break-with' => + array('ascii', 'foobar-foobar', 6, '-', true, 'foobar-foobar'), + 'cut-break-within' => + array('ascii', 'foobar-foobar', 7, '-', true, 'foobar-foobar'), + 'cut-break-within-end' => + array('ascii', 'foobar-', 7, '-', true, 'foobar-'), + 'cut-break-after' => + array('ascii', 'foobar-foobar', 5, '-', true, 'fooba-r-fooba-r'), + + // Standard no-cut tests + 'no-cut-single-line' => + array('utf-8', 'äbüöcß', 2, ' ', false, 'äbüöcß'), + 'no-cut-multi-line' => + array('utf-8', 'äbüöc ß äbüöcß', 2, "\n", false, "äbüöc\nß\näbüöcß"), + 'no-cut-multi-word' => + array('utf-8', 'äöü äöü äöü', 5, "\n", false, "äöü\näöü\näöü"), + + // Break no-cut tests + 'no-cut-break-before' => + array('ascii', 'foobar-foofoofoo', 8, '-', false, 'foobar-foofoofoo'), + 'no-cut-break-with' => + array('ascii', 'foobar-foobar', 6, '-', false, 'foobar-foobar'), + 'no-cut-break-within' => + array('ascii', 'foobar-foobar', 7, '-', false, 'foobar-foobar'), + 'no-cut-break-within-end' => + array('ascii', 'foobar-', 7, '-', false, 'foobar-'), + 'no-cut-break-after' => + array('ascii', 'foobar-foobar', 5, '-', false, 'foobar-foobar'), + ); + } + + /** + * @dataProvider wordWrapProvider + * @param string $encoding + * @param string $str + * @param integer $width + * @param string $break + * @param boolean $cut + * @param mixed $expected + */ + public function testWordWrap($encoding, $string, $width, $break, $cut, $expected) + { + $wrapper = $this->getWrapper($encoding); + if (!$wrapper) { + $this->markTestSkipped("Encoding {$encoding} not supported"); + } + + $result = $wrapper->wordWrap($string, $width, $break, $cut); + $this->assertSame($expected, $result); + } + + public function testWordWrapInvalidArgument() + { + $wrapper = $this->getWrapper(); + if (!$wrapper) { + $this->fail("Can't instantiate wrapper"); + } + + $this->setExpectedException( + 'Zend\Stdlib\Exception\InvalidArgumentException', + "Cannot force cut when width is zero" + ); + $wrapper->wordWrap('a', 0, "\n", true); + } + + public function strPadProvider() + { + return array( + // single-byte + 'left-padding_single-byte' => + array('ascii', 'aaa', 5, 'o', STR_PAD_LEFT, 'ooaaa'), + 'center-padding_single-byte' => + array('ascii', 'aaa', 6, 'o', STR_PAD_BOTH, 'oaaaoo'), + 'right-padding_single-byte' => + array('ascii', 'aaa', 5, 'o', STR_PAD_RIGHT, 'aaaoo'), + + // multi-byte + 'left-padding_multi-byte' => + array('utf-8', 'äää', 5, 'ö', STR_PAD_LEFT, 'ööäää'), + 'center-padding_multi-byte' => + array('utf-8', 'äää', 6, 'ö', STR_PAD_BOTH, 'öäääöö'), + 'right-padding_multi-byte' => + array('utf-8', 'äää', 5, 'ö', STR_PAD_RIGHT, 'äääöö'), + + // ZF-12186 + 'input-longer-than-pad-length' => + array('utf-8', 'äääöö', 2, 'ö', STR_PAD_RIGHT, 'äääöö'), + 'input-same-as-pad-length' => + array('utf-8', 'äääöö', 5, 'ö', STR_PAD_RIGHT, 'äääöö'), + 'negative-pad-length' => + array('utf-8', 'äääöö', -2, 'ö', STR_PAD_RIGHT, 'äääöö'), + ); + } + + /** + * @dataProvider strPadProvider + * @param string $encoding + * @param string $input + * @param integer $padLength + * @param string $padString + * @param integer $padType + * @param mixed $expected + * + * @group ZF-12186 + */ + public function testStrPad($encoding, $input, $padLength, $padString, $padType, $expected) + { + $wrapper = $this->getWrapper($encoding); + if (!$wrapper) { + $this->markTestSkipped("Encoding {$encoding} not supported"); + } + + $result = $wrapper->strPad($input, $padLength, $padString, $padType); + $this->assertSame($expected, $result); + } +} diff --git a/tests/ZendTest/Stdlib/StringWrapper/IconvTest.php b/tests/ZendTest/Stdlib/StringWrapper/IconvTest.php new file mode 100644 index 00000000000..ec3b0db24d8 --- /dev/null +++ b/tests/ZendTest/Stdlib/StringWrapper/IconvTest.php @@ -0,0 +1,49 @@ +fail('Missing expected Zend\Stdlib\Exception\ExtensionNotLoadedException'); + } catch (Exception\ExtensionNotLoadedException $e) { + $this->markTestSkipped('Missing ext/iconv'); + } + } + + parent::setUp(); + } + + protected function getWrapper($encoding = null, $convertEncoding = null) + { + if ($encoding === null) { + $supportedEncodings = Iconv::getSupportedEncodings(); + $encoding = array_shift($supportedEncodings); + } + + if (!Iconv::isSupported($encoding, $convertEncoding)) { + return false; + } + + $wrapper = new Iconv(); + $wrapper->setEncoding($encoding, $convertEncoding); + return $wrapper; + } +} diff --git a/tests/ZendTest/Stdlib/StringWrapper/IntlTest.php b/tests/ZendTest/Stdlib/StringWrapper/IntlTest.php new file mode 100644 index 00000000000..d3092f60b06 --- /dev/null +++ b/tests/ZendTest/Stdlib/StringWrapper/IntlTest.php @@ -0,0 +1,49 @@ +fail('Missing expected Zend\Stdlib\Exception\ExtensionNotLoadedException'); + } catch (Exception\ExtensionNotLoadedException $e) { + $this->markTestSkipped('Missing ext/intl'); + } + } + + parent::setUp(); + } + + protected function getWrapper($encoding = null, $convertEncoding = null) + { + if ($encoding === null) { + $supportedEncodings = Intl::getSupportedEncodings(); + $encoding = array_shift($supportedEncodings); + } + + if (!Intl::isSupported($encoding, $convertEncoding)) { + return false; + } + + $wrapper = new Intl(); + $wrapper->setEncoding($encoding, $convertEncoding); + return $wrapper; + } +} diff --git a/tests/ZendTest/Stdlib/StringWrapper/MbStringTest.php b/tests/ZendTest/Stdlib/StringWrapper/MbStringTest.php new file mode 100644 index 00000000000..d024afaa6b0 --- /dev/null +++ b/tests/ZendTest/Stdlib/StringWrapper/MbStringTest.php @@ -0,0 +1,49 @@ +fail('Missing expected Zend\Stdlib\Exception\ExtensionNotLoadedException'); + } catch (Exception\ExtensionNotLoadedException $e) { + $this->markTestSkipped('Missing ext/mbstring'); + } + } + + parent::setUp(); + } + + protected function getWrapper($encoding = null, $convertEncoding = null) + { + if ($encoding === null) { + $supportedEncodings = MbString::getSupportedEncodings(); + $encoding = array_shift($supportedEncodings); + } + + if (!MbString::isSupported($encoding, $convertEncoding)) { + return false; + } + + $wrapper = new MbString(); + $wrapper->setEncoding($encoding, $convertEncoding); + return $wrapper; + } +} diff --git a/tests/ZendTest/Stdlib/StringWrapper/NativeTest.php b/tests/ZendTest/Stdlib/StringWrapper/NativeTest.php new file mode 100644 index 00000000000..b54e41daad3 --- /dev/null +++ b/tests/ZendTest/Stdlib/StringWrapper/NativeTest.php @@ -0,0 +1,34 @@ +setEncoding($encoding, $convertEncoding); + return $wrapper; + } +} diff --git a/tests/ZendTest/Text/MultiByteTest.php b/tests/ZendTest/Text/MultiByteTest.php index b8e7779b67a..54089d080c9 100644 --- a/tests/ZendTest/Text/MultiByteTest.php +++ b/tests/ZendTest/Text/MultiByteTest.php @@ -20,249 +20,15 @@ */ class MultiByteTest extends \PHPUnit_Framework_TestCase { - /** - * Standard cut tests - */ - public function testWordWrapCutSingleLine() + public function testWordWrapTriggersDeprecatedError() { + $this->setExpectedException('PHPUnit_Framework_Error_Deprecated'); $line = Text\MultiByte::wordWrap('äbüöcß', 2, ' ', true); - $this->assertEquals('äb üö cß', $line); } - public function testWordWrapCutMultiLine() - { - $line = Text\MultiByte::wordWrap('äbüöc ß äbüöcß', 2, ' ', true); - $this->assertEquals('äb üö c ß äb üö cß', $line); - } - - public function testWordWrapCutMultiLineShortWords() - { - $line = Text\MultiByte::wordWrap('Ä very long wöööööööööööörd.', 8, "\n", true); - $this->assertEquals("Ä very\nlong\nwööööööö\nööööörd.", $line); - } - - public function testWordWrapCutMultiLineWithPreviousNewlines() - { - $line = Text\MultiByte::wordWrap("Ä very\nlong wöööööööööööörd.", 8, "\n", false); - $this->assertEquals("Ä very\nlong\nwöööööööööööörd.", $line); - } - - /** - * Long-Break tests - */ - public function testWordWrapLongBreak() - { - $line = Text\MultiByte::wordWrap("Ä very
long wöö
öööööööö
öörd.", 8, '
', false); - $this->assertEquals("Ä very
long wöö
öööööööö
öörd.", $line); - } - - /** - * Alternative cut tests - */ - public function testWordWrapCutBeginningSingleSpace() - { - $line = Text\MultiByte::wordWrap(' äüöäöü', 3, ' ', true); - $this->assertEquals(' äüö äöü', $line); - } - - public function testWordWrapCutEndingSingleSpace() - { - $line = Text\MultiByte::wordWrap('äüöäöü ', 3, ' ', true); - $this->assertEquals('äüö äöü ', $line); - } - - public function testWordWrapCutEndingSingleSpaceWithNonSpaceDivider() - { - $line = Text\MultiByte::wordWrap('äöüäöü ', 3, '-', true); - $this->assertEquals('äöü-äöü-', $line); - } - - public function testWordWrapCutEndingTwoSpaces() - { - $line = Text\MultiByte::wordWrap('äüöäöü ', 3, ' ', true); - $this->assertEquals('äüö äöü ', $line); - } - - public function testWordWrapNoCutEndingSingleSpace() - { - $line = Text\Multibyte::wordWrap('12345 ', 5, '-', false); - $this->assertEquals('12345-', $line); - } - - public function testWordWrapNoCutEndingTwoSpaces() - { - $line = Text\MultiByte::wordWrap('12345 ', 5, '-', false); - $this->assertEquals('12345- ', $line); - } - - public function testWordWrapCutEndingThreeSpaces() - { - $line = Text\MultiByte::wordWrap('äüöäöü ', 3, ' ', true); - $this->assertEquals('äüö äöü ', $line); - } - - public function testWordWrapCutEndingTwoBreaks() - { - $line = Text\MultiByte::wordWrap('äüöäöü--', 3, '-', true); - $this->assertEquals('äüö-äöü--', $line); - } - - public function testWordWrapCutTab() - { - $line = Text\MultiByte::wordWrap("äbü\töcß", 3, ' ', true); - $this->assertEquals("äbü \töc ß", $line); - } - - public function testWordWrapCutNewlineWithSpace() - { - $line = Text\MultiByte::wordWrap("äbü\nößt", 3, ' ', true); - $this->assertEquals("äbü \nöß t", $line); - } - - public function testWordWrapCutNewlineWithNewline() - { - $line = Text\MultiByte::wordWrap("äbü\nößte", 3, "\n", true); - $this->assertEquals("äbü\nößt\ne", $line); - } - - /** - * Break cut tests - */ - public function testWordWrapCutBreakBefore() - { - $line = Text\MultiByte::wordWrap('foobar-foofoofoo', 8, '-', true); - $this->assertEquals('foobar-foofoofo-o', $line); - } - - public function testWordWrapCutBreakWith() - { - $line = Text\MultiByte::wordWrap('foobar-foobar', 6, '-', true); - $this->assertEquals('foobar-foobar', $line); - } - - public function testWordWrapCutBreakWithin() - { - $line = Text\MultiByte::wordWrap('foobar-foobar', 7, '-', true); - $this->assertEquals('foobar-foobar', $line); - } - - public function testWordWrapCutBreakWithinEnd() - { - $line = Text\MultiByte::wordWrap('foobar-', 7, '-', true); - $this->assertEquals('foobar-', $line); - } - - public function testWordWrapCutBreakAfter() - { - $line = Text\MultiByte::wordWrap('foobar-foobar', 5, '-', true); - $this->assertEquals('fooba-r-fooba-r', $line); - } - - /** - * Standard no-cut tests - */ - public function testWordWrapNoCutSingleLine() - { - $line = Text\MultiByte::wordWrap('äbüöcß', 2, ' ', false); - $this->assertEquals('äbüöcß', $line); - } - - public function testWordWrapNoCutMultiLine() - { - $line = Text\MultiByte::wordWrap('äbüöc ß äbüöcß', 2, "\n", false); - $this->assertEquals("äbüöc\nß\näbüöcß", $line); - } - - public function testWordWrapNoCutMultiWord() - { - $line = Text\MultiByte::wordWrap('äöü äöü äöü', 5, "\n", false); - $this->assertEquals("äöü\näöü\näöü", $line); - } - - /** - * Break no-cut tests - */ - public function testWordWrapNoCutBreakBefore() - { - $line = Text\MultiByte::wordWrap('foobar-foofoofoo', 8, '-', false); - $this->assertEquals('foobar-foofoofoo', $line); - } - - public function testWordWrapNoCutBreakWith() - { - $line = Text\MultiByte::wordWrap('foobar-foobar', 6, '-', false); - $this->assertEquals('foobar-foobar', $line); - } - - public function testWordWrapNoCutBreakWithin() - { - $line = Text\MultiByte::wordWrap('foobar-foobar', 7, '-', false); - $this->assertEquals('foobar-foobar', $line); - } - - public function testWordWrapNoCutBreakWithinEnd() - { - $line = Text\MultiByte::wordWrap('foobar-', 7, '-', false); - $this->assertEquals('foobar-', $line); - } - - public function testWordWrapNoCutBreakAfter() - { - $line = Text\MultiByte::wordWrap('foobar-foobar', 5, '-', false); - $this->assertEquals('foobar-foobar', $line); - } - - /** - * Pad tests - */ - public function testLeftPad() - { - $text = Text\MultiByte::strPad('äää', 5, 'ö', STR_PAD_LEFT); - $this->assertEquals('ööäää', $text); - } - - public function testCenterPad() - { - $text = Text\MultiByte::strPad('äää', 6, 'ö', STR_PAD_BOTH); - $this->assertEquals('öäääöö', $text); - } - - public function testRightPad() - { - $text = Text\MultiByte::strPad('äääöö', 5, 'ö', STR_PAD_RIGHT); - $this->assertEquals('äääöö', $text); - } - - public function testWordWrapInvalidArgument() - { - $this->setExpectedException('Zend\Text\Exception\InvalidArgumentException', "Cannot force cut when width is zero"); - Text\MultiByte::wordWrap('a', 0, "\n", true); - } - - /** - * @group ZF-12186 - */ - public function testPadInputLongerThanPadLength() + public function testStrPadTriggersDeprecatedError() { + $this->setExpectedException('PHPUnit_Framework_Error_Deprecated'); $text = Text\MultiByte::strPad('äääöö', 2, 'ö'); - $this->assertEquals('äääöö', $text); - } - - /** - * @group ZF-12186 - */ - public function testPadInputSameAsPadLength() - { - $text = Text\MultiByte::strPad('äääöö', 5, 'ö'); - $this->assertEquals('äääöö', $text); - } - - /** - * @group ZF-12186 - */ - public function testPadNegativePadLength() - { - $text = Text\MultiByte::strPad('äääöö', -2, 'ö'); - $this->assertEquals('äääöö', $text); } }