| Code Coverage | ||||||||||
| Lines | Functions and Methods | Classes and Traits | ||||||||
| Total |  | 100.00% | 40 / 40 |  | 100.00% | 4 / 4 | CRAP |  | 100.00% | 1 / 1 | 
| Huffman |  | 100.00% | 40 / 40 |  | 100.00% | 4 / 4 | 21 |  | 100.00% | 1 / 1 | 
| getDictionary |  | 100.00% | 1 / 1 |  | 100.00% | 1 / 1 | 1 | |||
| setDictionary |  | 100.00% | 1 / 1 |  | 100.00% | 1 / 1 | 1 | |||
| encode |  | 100.00% | 15 / 15 |  | 100.00% | 1 / 1 | 7 | |||
| decode |  | 100.00% | 23 / 23 |  | 100.00% | 1 / 1 | 12 | |||
| 1 | <?php | 
| 2 | /** | 
| 3 | * Jingga | 
| 4 | * | 
| 5 | * PHP Version 8.1 | 
| 6 | * | 
| 7 | * @package phpOMS\Utils\Encoding\Huffman | 
| 8 | * @copyright Dennis Eichhorn | 
| 9 | * @license OMS License 2.0 | 
| 10 | * @version 1.0.0 | 
| 11 | * @link https://jingga.app | 
| 12 | */ | 
| 13 | declare(strict_types=1); | 
| 14 | |
| 15 | namespace phpOMS\Utils\Encoding\Huffman; | 
| 16 | |
| 17 | /** | 
| 18 | * Gray encoding class | 
| 19 | * | 
| 20 | * @package phpOMS\Utils\Encoding\Huffman | 
| 21 | * @license OMS License 2.0 | 
| 22 | * @link https://jingga.app | 
| 23 | * @since 1.0.0 | 
| 24 | */ | 
| 25 | final class Huffman | 
| 26 | { | 
| 27 | /** | 
| 28 | * Huffman dictionary. | 
| 29 | * | 
| 30 | * @var null|Dictionary | 
| 31 | * @since 1.0.0 | 
| 32 | */ | 
| 33 | private ?Dictionary $dictionary = null; | 
| 34 | |
| 35 | /** | 
| 36 | * Get dictionary | 
| 37 | * | 
| 38 | * @return Dictionary | 
| 39 | * | 
| 40 | * @since 1.0.0 | 
| 41 | */ | 
| 42 | public function getDictionary() : ?Dictionary | 
| 43 | { | 
| 44 | return $this->dictionary; | 
| 45 | } | 
| 46 | |
| 47 | /** | 
| 48 | * Set dictionary | 
| 49 | * | 
| 50 | * @param Dictionary $dictionary Huffman dictionary | 
| 51 | * | 
| 52 | * @return void | 
| 53 | * | 
| 54 | * @since 1.0.0 | 
| 55 | */ | 
| 56 | public function setDictionary(Dictionary $dictionary) : void | 
| 57 | { | 
| 58 | $this->dictionary = $dictionary; | 
| 59 | } | 
| 60 | |
| 61 | /** | 
| 62 | * Encode. | 
| 63 | * | 
| 64 | * @param string $source Source to encode | 
| 65 | * | 
| 66 | * @return string | 
| 67 | * | 
| 68 | * @since 1.0.0 | 
| 69 | */ | 
| 70 | public function encode(string $source) : string | 
| 71 | { | 
| 72 | if (empty($source)) { | 
| 73 | return ''; | 
| 74 | } | 
| 75 | |
| 76 | if (!isset($this->dictionary)) { | 
| 77 | $this->dictionary = new Dictionary($source); | 
| 78 | } | 
| 79 | |
| 80 | $binary = ''; | 
| 81 | for ($i = 0; isset($source[$i]); ++$i) { | 
| 82 | $binary .= $this->dictionary->get($source[$i]); | 
| 83 | } | 
| 84 | |
| 85 | $splittedBinaryString = \str_split('1' . $binary . '1', 8); | 
| 86 | $binary = ''; | 
| 87 | |
| 88 | if ($splittedBinaryString === false) { | 
| 89 | return $binary; // @codeCoverageIgnore | 
| 90 | } | 
| 91 | |
| 92 | foreach ($splittedBinaryString as $i => $c) { | 
| 93 | while (\strlen($c) < 8) { | 
| 94 | $c .= '0'; | 
| 95 | } | 
| 96 | |
| 97 | $binary .= \chr((int) \bindec($c)); | 
| 98 | } | 
| 99 | |
| 100 | return $binary; | 
| 101 | } | 
| 102 | |
| 103 | /** | 
| 104 | * Decode. | 
| 105 | * | 
| 106 | * @param string $raw Raw to decode | 
| 107 | * | 
| 108 | * @return string | 
| 109 | * | 
| 110 | * @throws \Exception | 
| 111 | * | 
| 112 | * @since 1.0.0 | 
| 113 | */ | 
| 114 | public function decode(string $raw) : string | 
| 115 | { | 
| 116 | if (empty($raw) || $this->dictionary === null) { | 
| 117 | return ''; | 
| 118 | } | 
| 119 | |
| 120 | $binary = ''; | 
| 121 | $rawLenght = \strlen($raw); | 
| 122 | $source = ''; | 
| 123 | |
| 124 | for ($i = 0; $i < $rawLenght; ++$i) { | 
| 125 | $decbin = \decbin(\ord($raw[$i])); | 
| 126 | |
| 127 | while (\strlen($decbin) < 8) { | 
| 128 | $decbin = '0' . $decbin; | 
| 129 | } | 
| 130 | |
| 131 | if ($i === 0) { | 
| 132 | $pos = \strpos($decbin, '1'); | 
| 133 | |
| 134 | if ($pos === false) { | 
| 135 | throw new \Exception(); // @codeCoverageIgnore | 
| 136 | } | 
| 137 | |
| 138 | $decbin = \substr($decbin, $pos + 1); | 
| 139 | if ($decbin === false) { | 
| 140 | throw new \Exception(); // @codeCoverageIgnore | 
| 141 | } | 
| 142 | } | 
| 143 | |
| 144 | if ($i + 1 === $rawLenght) { | 
| 145 | $pos = \strrpos($decbin, '1'); | 
| 146 | |
| 147 | if ($pos === false) { | 
| 148 | throw new \Exception(); // @codeCoverageIgnore | 
| 149 | } | 
| 150 | |
| 151 | $decbin = \substr($decbin, 0, $pos); | 
| 152 | if ($decbin === false) { | 
| 153 | throw new \Exception(); // @codeCoverageIgnore | 
| 154 | } | 
| 155 | } | 
| 156 | |
| 157 | $binary .= $decbin; | 
| 158 | |
| 159 | while (($entry = $this->dictionary->getEntry($binary)) !== null) { | 
| 160 | $source .= $entry; | 
| 161 | } | 
| 162 | } | 
| 163 | |
| 164 | return $source; | 
| 165 | } | 
| 166 | } |