From c76c70b3f2cb691e4a57e4771005e176121bdde4 Mon Sep 17 00:00:00 2001 From: smiley Date: Thu, 21 Nov 2024 16:38:38 +0100 Subject: [PATCH] :octocat: simplify Number and AlphaNum classes (backport from dev-main) --- src/Data/AlphaNum.php | 82 ++++++++++++++++++++----------------------- src/Data/Number.php | 58 ++++++++++-------------------- 2 files changed, 57 insertions(+), 83 deletions(-) diff --git a/src/Data/AlphaNum.php b/src/Data/AlphaNum.php index a62cb5474..0d224efc7 100644 --- a/src/Data/AlphaNum.php +++ b/src/Data/AlphaNum.php @@ -11,7 +11,7 @@ namespace chillerlan\QRCode\Data; use chillerlan\QRCode\Common\{BitBuffer, Mode}; -use function array_flip, ceil, intdiv, str_split; +use function ceil, intdiv, preg_match, strpos; /** * Alphanumeric mode: 0 to 9, A to Z, space, $ % * + - . / : @@ -24,18 +24,9 @@ final class AlphaNum extends QRDataModeAbstract{ /** * ISO/IEC 18004:2000 Table 5 * - * @var int[] + * @var string */ - private const CHAR_TO_ORD = [ - // phpcs:ignore - '0' => 0, '1' => 1, '2' => 2, '3' => 3, '4' => 4, '5' => 5, '6' => 6, '7' => 7, - // phpcs:ignore - '8' => 8, '9' => 9, 'A' => 10, 'B' => 11, 'C' => 12, 'D' => 13, 'E' => 14, 'F' => 15, - 'G' => 16, 'H' => 17, 'I' => 18, 'J' => 19, 'K' => 20, 'L' => 21, 'M' => 22, 'N' => 23, - 'O' => 24, 'P' => 25, 'Q' => 26, 'R' => 27, 'S' => 28, 'T' => 29, 'U' => 30, 'V' => 31, - 'W' => 32, 'X' => 33, 'Y' => 34, 'Z' => 35, ' ' => 36, '$' => 37, '%' => 38, '*' => 39, - '+' => 40, '-' => 41, '.' => 42, '/' => 43, ':' => 44, - ]; + private const CHAR_MAP = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ $%*+-./:'; /** * @inheritDoc @@ -53,18 +44,7 @@ final class AlphaNum extends QRDataModeAbstract{ * @inheritDoc */ public static function validateString(string $string):bool{ - - if($string === ''){ - return false; - } - - foreach(str_split($string) as $chr){ - if(!isset(self::CHAR_TO_ORD[$chr])){ - return false; - } - } - - return true; + return (bool)preg_match('/^[A-Z\d %$*+-.:\/]+$/', $string); } /** @@ -80,12 +60,15 @@ final class AlphaNum extends QRDataModeAbstract{ // encode 2 characters in 11 bits for($i = 0; ($i + 1) < $len; $i += 2){ - $bitBuffer->put((self::CHAR_TO_ORD[$this->data[$i]] * 45 + self::CHAR_TO_ORD[$this->data[($i + 1)]]), 11); + $bitBuffer->put( + ($this->ord($this->data[$i]) * 45 + $this->ord($this->data[($i + 1)])), + 11, + ); } // encode a remaining character in 6 bits if($i < $len){ - $bitBuffer->put(self::CHAR_TO_ORD[$this->data[$i]], 6); + $bitBuffer->put($this->ord($this->data[$i]), 6); } return $this; @@ -97,19 +80,7 @@ final class AlphaNum extends QRDataModeAbstract{ * @throws \chillerlan\QRCode\Data\QRCodeDataException */ public static function decodeSegment(BitBuffer $bitBuffer, int $versionNumber):string{ - $length = $bitBuffer->read(self::getLengthBits($versionNumber)); - $charmap = array_flip(self::CHAR_TO_ORD); - - // @todo - $toAlphaNumericChar = function(int $ord) use ($charmap):string{ - - if(isset($charmap[$ord])){ - return $charmap[$ord]; - } - - throw new QRCodeDataException('invalid character value: '.$ord); - }; - + $length = $bitBuffer->read(self::getLengthBits($versionNumber)); $result = ''; // Read two characters at a time while($length > 1){ @@ -118,9 +89,9 @@ final class AlphaNum extends QRDataModeAbstract{ throw new QRCodeDataException('not enough bits available'); // @codeCoverageIgnore } - $nextTwoCharsBits = $bitBuffer->read(11); - $result .= $toAlphaNumericChar(intdiv($nextTwoCharsBits, 45)); - $result .= $toAlphaNumericChar($nextTwoCharsBits % 45); + $nextTwoCharsBits = $bitBuffer->read(11); + $result .= self::chr(intdiv($nextTwoCharsBits, 45)); + $result .= self::chr($nextTwoCharsBits % 45); $length -= 2; } @@ -130,10 +101,35 @@ final class AlphaNum extends QRDataModeAbstract{ throw new QRCodeDataException('not enough bits available'); // @codeCoverageIgnore } - $result .= $toAlphaNumericChar($bitBuffer->read(6)); + $result .= self::chr($bitBuffer->read(6)); } return $result; } + /** + * @throws \chillerlan\QRCode\Data\QRCodeDataException + */ + private function ord(string $chr):int{ + $ord = strpos(self::CHAR_MAP, $chr); + + if($ord === false){ + throw new QRCodeDataException('invalid character'); // @codeCoverageIgnore + } + + return $ord; + } + + /** + * @throws \chillerlan\QRCode\Data\QRCodeDataException + */ + private static function chr(int $ord):string{ + + if($ord < 0 || $ord > 44){ + throw new QRCodeDataException('invalid character code'); // @codeCoverageIgnore + } + + return self::CHAR_MAP[$ord]; + } + } diff --git a/src/Data/Number.php b/src/Data/Number.php index 285be3736..deae33306 100644 --- a/src/Data/Number.php +++ b/src/Data/Number.php @@ -21,13 +21,6 @@ use function array_flip, ceil, intdiv, str_split, substr, unpack; */ final class Number extends QRDataModeAbstract{ - /** - * @var int[] - */ - private const NUMBER_TO_ORD = [ - '0' => 0, '1' => 1, '2' => 2, '3' => 3, '4' => 4, '5' => 5, '6' => 6, '7' => 7, '8' => 8, '9' => 9, - ]; - /** * @inheritDoc */ @@ -44,18 +37,7 @@ final class Number extends QRDataModeAbstract{ * @inheritDoc */ public static function validateString(string $string):bool{ - - if($string === ''){ - return false; - } - - foreach(str_split($string) as $chr){ - if(!isset(self::NUMBER_TO_ORD[$chr])){ - return false; - } - } - - return true; + return (bool)preg_match('/^\d+$/', $string); } /** @@ -95,12 +77,20 @@ final class Number extends QRDataModeAbstract{ /** * get the code for the given numeric string + * + * @throws \chillerlan\QRCode\Data\QRCodeDataException */ private function parseInt(string $string):int{ $num = 0; - foreach(unpack('C*', $string) as $chr){ - $num = ($num * 10 + $chr - 48); + $ords = unpack('C*', $string); + + if($ords === false){ + throw new QRCodeDataException('unpack() error'); + } + + foreach($ords as $ord){ + $num = ($num * 10 + $ord - 48); } return $num; @@ -112,19 +102,7 @@ final class Number extends QRDataModeAbstract{ * @throws \chillerlan\QRCode\Data\QRCodeDataException */ public static function decodeSegment(BitBuffer $bitBuffer, int $versionNumber):string{ - $length = $bitBuffer->read(self::getLengthBits($versionNumber)); - $charmap = array_flip(self::NUMBER_TO_ORD); - - // @todo - $toNumericChar = function(int $ord) use ($charmap):string{ - - if(isset($charmap[$ord])){ - return $charmap[$ord]; - } - - throw new QRCodeDataException('invalid character value: '.$ord); - }; - + $length = $bitBuffer->read(self::getLengthBits($versionNumber)); $result = ''; // Read three digits at a time while($length >= 3){ @@ -139,9 +117,9 @@ final class Number extends QRDataModeAbstract{ throw new QRCodeDataException('error decoding numeric value'); } - $result .= $toNumericChar(intdiv($threeDigitsBits, 100)); - $result .= $toNumericChar(intdiv($threeDigitsBits, 10) % 10); - $result .= $toNumericChar($threeDigitsBits % 10); + $result .= intdiv($threeDigitsBits, 100); + $result .= (intdiv($threeDigitsBits, 10) % 10); + $result .= ($threeDigitsBits % 10); $length -= 3; } @@ -158,8 +136,8 @@ final class Number extends QRDataModeAbstract{ throw new QRCodeDataException('error decoding numeric value'); } - $result .= $toNumericChar(intdiv($twoDigitsBits, 10)); - $result .= $toNumericChar($twoDigitsBits % 10); + $result .= intdiv($twoDigitsBits, 10); + $result .= ($twoDigitsBits % 10); } elseif($length === 1){ // One digit left over to read @@ -173,7 +151,7 @@ final class Number extends QRDataModeAbstract{ throw new QRCodeDataException('error decoding numeric value'); } - $result .= $toNumericChar($digitBits); + $result .= $digitBits; } return $result;