Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
40 / 40
100.00% covered (success)
100.00%
4 / 4
CRAP
100.00% covered (success)
100.00%
1 / 1
Huffman
100.00% covered (success)
100.00%
40 / 40
100.00% covered (success)
100.00%
4 / 4
21
100.00% covered (success)
100.00%
1 / 1
 getDictionary
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 setDictionary
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 encode
100.00% covered (success)
100.00%
15 / 15
100.00% covered (success)
100.00%
1 / 1
7
 decode
100.00% covered (success)
100.00%
23 / 23
100.00% covered (success)
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 */
13declare(strict_types=1);
14
15namespace 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 */
25final 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}