├── LICENSE
├── README.md
├── logo.webp
└── src
└── MorseCode.php
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2024 Ramazan Çetinkaya
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
Morse Code Library
2 |
3 | A modern PHP library for encoding and decoding Morse code with extended configuration options.
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 | Report a Bug
12 | ·
13 | New Pull Request
14 |
15 |
16 |
17 | ## Features
18 |
19 | - Encode and decode Morse code seamlessly.
20 | - Customizable delimiters for letters and words.
21 | - Multiple handling options for unknown characters.
22 | - Configurable case preservation.
23 | - Structured error handling with custom exceptions.
24 | - Fully object-oriented and extensible.
25 |
26 | ## Installation
27 |
28 | This library can be easily installed using [Composer](https://getcomposer.org/), a modern PHP dependency manager.
29 |
30 | ### Step 1: Install Composer
31 |
32 | If you don't have Composer installed, you can download and install it by following the instructions on the [official Composer website](https://getcomposer.org/download/).
33 |
34 | ### Step 2: Install the Library
35 |
36 | Once Composer is installed, you can install the `morse-code` library by running the following command in your project's root directory:
37 |
38 | ```bash
39 | composer require ramazancetinkaya/morse-code
40 | ```
41 |
42 | _Alternatively, download the source code and include it in your project manually._
43 |
44 | ### Requirements
45 |
46 | - PHP 8.0 or higher.
47 | - No additional dependencies.
48 |
49 | ## Usage
50 |
51 | ```php
52 | require 'vendor/autoload.php'; // Include Composer's autoloader
53 |
54 | use ramazancetinkaya\{MorseTranslator, MorseCodeConfig, UnknownCharHandling};
55 |
56 | // Create a configuration where unknown characters are replaced with '?'
57 | // and we separate letters with a single space, words with ' / ',
58 | // and DO NOT preserve original case (defaults to uppercase).
59 | $config = new MorseCodeConfig(
60 | unknownCharHandling: UnknownCharHandling::REPLACE,
61 | replacementChar: '?',
62 | preserveCase: false,
63 | letterDelimiter: ' ', // single space between letters
64 | wordDelimiter: ' / ' // slash and spaces between words
65 | );
66 |
67 | // Create the translator
68 | $translator = new MorseTranslator();
69 |
70 | // Sample text to encode
71 | $text = "Hello, World!";
72 |
73 | try {
74 | // Encoding
75 | $encoded = $translator->encode($text, $config);
76 | echo "Original: {$text}\n";
77 | echo "Encoded: {$encoded}\n";
78 |
79 | // Decoding
80 | $decoded = $translator->decode($encoded, $config);
81 | echo "Decoded: {$decoded}\n";
82 | } catch (MorseCodeException $exception) {
83 | // Handle or log the exception
84 | echo "Morse Code Error: " . $exception->getMessage() . "\n";
85 | }
86 | ```
87 |
88 | ## Configuration Options
89 |
90 | | Option | Description |
91 | |----------------------|-------------|
92 | | `unknownCharHandling` | Defines how unknown characters are handled (`IGNORE`, `REPLACE`, `THROW_EXCEPTION`). |
93 | | `replacementChar` | Specifies the character used when `REPLACE` mode is enabled. |
94 | | `preserveCase` | If `true`, preserves original case; otherwise, converts text to uppercase. |
95 | | `letterDelimiter` | Defines the separator between Morse code letters. |
96 | | `wordDelimiter` | Defines the separator between Morse code words. |
97 |
98 | ## License
99 |
100 | This project is licensed under the MIT License. See the [LICENSE](LICENSE) file for more details.
101 |
102 | ## Contributing
103 |
104 | Contributions are welcome! Please feel free to submit a pull request or open an issue for any enhancements or bug fixes.
105 |
106 | ## Author
107 |
108 | Developed by [Ramazan Çetinkaya](https://github.com/ramazancetinkaya).
109 |
--------------------------------------------------------------------------------
/logo.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ramazancetinkaya/morse-code/b75566d7c917197032e064c2a9c6997452cdfd0a/logo.webp
--------------------------------------------------------------------------------
/src/MorseCode.php:
--------------------------------------------------------------------------------
1 |
15 | * @version 1.0.0
16 | * @link https://github.com/ramazancetinkaya/morse-code
17 | *
18 | * @see https://en.wikipedia.org/wiki/Morse_code
19 | */
20 |
21 | namespace ramazancetinkaya;
22 |
23 | /**
24 | * Enum that defines how to handle unknown characters when encoding/decoding.
25 | *
26 | * @package MorseCode
27 | */
28 | enum UnknownCharHandling: string
29 | {
30 | /**
31 | * If an unknown character is encountered, skip it entirely (omit).
32 | */
33 | case IGNORE = 'ignore';
34 |
35 | /**
36 | * If an unknown character is encountered, replace it with a placeholder character.
37 | */
38 | case REPLACE = 'replace';
39 |
40 | /**
41 | * If an unknown character is encountered, throw an exception.
42 | */
43 | case THROW_EXCEPTION = 'throw_exception';
44 | }
45 |
46 | /**
47 | * Custom exception class for Morse Code errors.
48 | *
49 | * @package MorseCode
50 | */
51 | class MorseCodeException extends \Exception
52 | {
53 | // Reserved for future enhancements or custom exception behavior.
54 | }
55 |
56 | /**
57 | * This class provides the core mapping between characters and Morse code representations.
58 | * It also generates the reverse mapping from Morse code to characters.
59 | *
60 | * @package MorseCode
61 | */
62 | class MorseCodeDictionary
63 | {
64 | /**
65 | * @var array Maps individual characters (A, B, 1, etc.) to Morse code (.-, -..., etc.)
66 | */
67 | private static array $charToMorseMap = [
68 | 'A' => '.-', 'B' => '-...', 'C' => '-.-.', 'D' => '-..',
69 | 'E' => '.', 'F' => '..-.', 'G' => '--.', 'H' => '....',
70 | 'I' => '..', 'J' => '.---', 'K' => '-.-', 'L' => '.-..',
71 | 'M' => '--', 'N' => '-.', 'O' => '---', 'P' => '.--.',
72 | 'Q' => '--.-', 'R' => '.-.', 'S' => '...', 'T' => '-',
73 | 'U' => '..-', 'V' => '...-', 'W' => '.--', 'X' => '-..-',
74 | 'Y' => '-.--', 'Z' => '--..',
75 | '0' => '-----', '1' => '.----', '2' => '..---', '3' => '...--',
76 | '4' => '....-', '5' => '.....', '6' => '-....', '7' => '--...',
77 | '8' => '---..', '9' => '----.',
78 | '.' => '.-.-.-', ',' => '--..--', '?' => '..--..', ':' => '---...',
79 | ';' => '-.-.-.', '!' => '-.-.--', '-' => '-....-', '/' => '-..-.',
80 | '@' => '.--.-.', '(' => '-.--.', ')' => '-.--.-', '&' => '.-...',
81 | '=' => '-...-', '+' => '.-.-.'
82 | ];
83 |
84 | /**
85 | * @var array Maps Morse code (.-, -..., etc.) back to individual characters (A, B, 1, etc.)
86 | */
87 | private static array $morseToCharMap = [];
88 |
89 | /**
90 | * Initializes the reverse mapping from Morse code to character.
91 | * This should be called once, typically on library load.
92 | *
93 | * @return void
94 | */
95 | public static function init(): void
96 | {
97 | if (empty(self::$morseToCharMap)) {
98 | foreach (self::$charToMorseMap as $char => $morse) {
99 | self::$morseToCharMap[$morse] = $char;
100 | }
101 | }
102 | }
103 |
104 | /**
105 | * Returns the Morse code representation of a character.
106 | *
107 | * @param string $char The character to map (already uppercase recommended).
108 | * @return string|null The corresponding Morse code or null if not found.
109 | */
110 | public static function getMorseCodeForChar(string $char): ?string
111 | {
112 | return self::$charToMorseMap[$char] ?? null;
113 | }
114 |
115 | /**
116 | * Returns the character representation of a Morse code token.
117 | *
118 | * @param string $morse The Morse code token (e.g. "-.-").
119 | * @return string|null The corresponding character or null if not found.
120 | */
121 | public static function getCharForMorseCode(string $morse): ?string
122 | {
123 | return self::$morseToCharMap[$morse] ?? null;
124 | }
125 | }
126 |
127 | // Initialize the dictionary mapping
128 | MorseCodeDictionary::init();
129 |
130 | /**
131 | * Configuration class for customizing the encoding/decoding process.
132 | *
133 | * @package MorseCode
134 | */
135 | class MorseCodeConfig
136 | {
137 | /**
138 | * @param UnknownCharHandling $unknownCharHandling How to handle unknown characters.
139 | * @param string $replacementChar Character used when unknownCharHandling = REPLACE.
140 | * @param bool $preserveCase Whether to preserve the original case of the text.
141 | * (If false, everything is converted to uppercase for encoding.)
142 | * @param string $letterDelimiter Delimiter inserted between letters in Morse code.
143 | * @param string $wordDelimiter Delimiter inserted between words in Morse code.
144 | */
145 | public function __construct(
146 | private UnknownCharHandling $unknownCharHandling = UnknownCharHandling::THROW_EXCEPTION,
147 | private string $replacementChar = '?',
148 | private bool $preserveCase = false,
149 | private string $letterDelimiter = ' ',
150 | private string $wordDelimiter = ' / '
151 | ) {
152 | }
153 |
154 | /**
155 | * @return UnknownCharHandling
156 | */
157 | public function getUnknownCharHandling(): UnknownCharHandling
158 | {
159 | return $this->unknownCharHandling;
160 | }
161 |
162 | /**
163 | * @return string
164 | */
165 | public function getReplacementChar(): string
166 | {
167 | return $this->replacementChar;
168 | }
169 |
170 | /**
171 | * @return bool
172 | */
173 | public function shouldPreserveCase(): bool
174 | {
175 | return $this->preserveCase;
176 | }
177 |
178 | /**
179 | * @return string
180 | */
181 | public function getLetterDelimiter(): string
182 | {
183 | return $this->letterDelimiter;
184 | }
185 |
186 | /**
187 | * @return string
188 | */
189 | public function getWordDelimiter(): string
190 | {
191 | return $this->wordDelimiter;
192 | }
193 | }
194 |
195 | /**
196 | * A class responsible for encoding plain text into Morse code.
197 | *
198 | * @package MorseCode
199 | */
200 | class MorseEncoder
201 | {
202 | /**
203 | * Encode a plain text string into Morse code.
204 | * This method splits text into words, encodes each word's letters,
205 | * and joins them using configured delimiters.
206 | *
207 | * @param string $text The plain text to encode.
208 | * @param MorseCodeConfig $config Configuration controlling behavior and delimiters.
209 | *
210 | * @return string The resulting Morse code.
211 | *
212 | * @throws MorseCodeException If an unknown character is encountered and THROW_EXCEPTION is set.
213 | */
214 | public function encode(string $text, MorseCodeConfig $config): string
215 | {
216 | // If case is not preserved, convert to uppercase
217 | $preparedText = $config->shouldPreserveCase() ? $text : mb_strtoupper($text);
218 |
219 | // Split the text into words by whitespace
220 | $words = preg_split('/\s+/', trim($preparedText)) ?: [];
221 | $encoded = [];
222 |
223 | foreach ($words as $word) {
224 | $letters = $this->encodeWord($word, $config);
225 | // Join letters with the letter delimiter
226 | $encoded[] = implode($config->getLetterDelimiter(), $letters);
227 | }
228 |
229 | // Join words with the word delimiter
230 | return implode($config->getWordDelimiter(), $encoded);
231 | }
232 |
233 | /**
234 | * Encode a single word into an array of Morse code tokens (one per letter).
235 | *
236 | * @param string $word Word to encode (already trimmed).
237 | * @param MorseCodeConfig $config Configuration controlling unknown characters.
238 | *
239 | * @return string[] Array of Morse code tokens.
240 | *
241 | * @throws MorseCodeException If an unknown character is encountered and THROW_EXCEPTION is set.
242 | */
243 | private function encodeWord(string $word, MorseCodeConfig $config): array
244 | {
245 | $morseTokens = [];
246 |
247 | for ($i = 0, $length = mb_strlen($word); $i < $length; $i++) {
248 | $char = mb_substr($word, $i, 1);
249 | $morseValue = MorseCodeDictionary::getMorseCodeForChar($char);
250 |
251 | if ($morseValue === null) {
252 | // Handle unknown char scenario
253 | switch ($config->getUnknownCharHandling()) {
254 | case UnknownCharHandling::IGNORE:
255 | // Skip entirely
256 | continue 2;
257 |
258 | case UnknownCharHandling::REPLACE:
259 | // Use the replacement character
260 | $morseTokens[] = $config->getReplacementChar();
261 | continue 2;
262 |
263 | case UnknownCharHandling::THROW_EXCEPTION:
264 | throw new MorseCodeException(
265 | sprintf('Unknown character encountered during encoding: "%s"', $char)
266 | );
267 | }
268 | }
269 |
270 | $morseTokens[] = $morseValue;
271 | }
272 |
273 | return $morseTokens;
274 | }
275 | }
276 |
277 | /**
278 | * A class responsible for decoding Morse code into plain text.
279 | *
280 | * @package MorseCode
281 | */
282 | class MorseDecoder
283 | {
284 | /**
285 | * Decode a Morse code string back into plain text.
286 | * This method splits Morse code into word tokens, then splits those word tokens into
287 | * letter tokens, and reconstructs the original text.
288 | *
289 | * @param string $morseCode The Morse code string to decode.
290 | * @param MorseCodeConfig $config Decoding configuration (delimiters, unknown handling, etc.).
291 | *
292 | * @return string Plain text decoded from the Morse code.
293 | *
294 | * @throws MorseCodeException If an unknown Morse token is encountered and THROW_EXCEPTION is set.
295 | */
296 | public function decode(string $morseCode, MorseCodeConfig $config): string
297 | {
298 | $trimmed = trim($morseCode);
299 |
300 | if ($trimmed === '') {
301 | return '';
302 | }
303 |
304 | // Split Morse code into word blocks
305 | $wordTokens = explode($config->getWordDelimiter(), $trimmed);
306 | $decodedWords = [];
307 |
308 | foreach ($wordTokens as $wordToken) {
309 | $decodedWords[] = $this->decodeWord($wordToken, $config);
310 | }
311 |
312 | // Join the decoded words with a space (to form a sentence)
313 | return implode(' ', $decodedWords);
314 | }
315 |
316 | /**
317 | * Decode a single word's Morse code (e.g. multiple letters joined by letterDelimiter).
318 | *
319 | * @param string $wordToken Morse code representing a single word.
320 | * @param MorseCodeConfig $config Configuration for decoding.
321 | *
322 | * @return string Decoded plain text word.
323 | *
324 | * @throws MorseCodeException If an unknown Morse token is encountered and THROW_EXCEPTION is set.
325 | */
326 | private function decodeWord(string $wordToken, MorseCodeConfig $config): string
327 | {
328 | $letterTokens = explode($config->getLetterDelimiter(), $wordToken);
329 | $decodedLetters = [];
330 |
331 | foreach ($letterTokens as $token) {
332 | $char = MorseCodeDictionary::getCharForMorseCode($token);
333 |
334 | // Unknown Morse code sequence handling
335 | if ($char === null) {
336 | switch ($config->getUnknownCharHandling()) {
337 | case UnknownCharHandling::IGNORE:
338 | // Skip this token
339 | continue 2;
340 |
341 | case UnknownCharHandling::REPLACE:
342 | $decodedLetters[] = $config->getReplacementChar();
343 | continue 2;
344 |
345 | case UnknownCharHandling::THROW_EXCEPTION:
346 | throw new MorseCodeException(
347 | sprintf('Unknown Morse code token encountered: "%s"', $token)
348 | );
349 | }
350 | }
351 |
352 | $decodedLetters[] = $char;
353 | }
354 |
355 | return implode('', $decodedLetters);
356 | }
357 | }
358 |
359 | /**
360 | * A unified facade that provides easy access to encoding and decoding functionalities.
361 | *
362 | * @package MorseCode
363 | */
364 | class MorseTranslator
365 | {
366 | /**
367 | * @var MorseEncoder
368 | */
369 | private MorseEncoder $encoder;
370 |
371 | /**
372 | * @var MorseDecoder
373 | */
374 | private MorseDecoder $decoder;
375 |
376 | /**
377 | * MorseTranslator constructor.
378 | *
379 | * @param MorseEncoder|null $encoder Custom encoder; if null, a default encoder is used.
380 | * @param MorseDecoder|null $decoder Custom decoder; if null, a default decoder is used.
381 | */
382 | public function __construct(MorseEncoder $encoder = null, MorseDecoder $decoder = null)
383 | {
384 | $this->encoder = $encoder ?? new MorseEncoder();
385 | $this->decoder = $decoder ?? new MorseDecoder();
386 | }
387 |
388 | /**
389 | * Encode plain text into Morse code.
390 | *
391 | * @param string $text The text to encode.
392 | * @param MorseCodeConfig $config Configuration object.
393 | *
394 | * @return string The resulting Morse code.
395 | *
396 | * @throws MorseCodeException
397 | */
398 | public function encode(string $text, MorseCodeConfig $config): string
399 | {
400 | return $this->encoder->encode($text, $config);
401 | }
402 |
403 | /**
404 | * Decode Morse code into plain text.
405 | *
406 | * @param string $morseCode The Morse code to decode.
407 | * @param MorseCodeConfig $config Configuration object.
408 | *
409 | * @return string The decoded plain text.
410 | *
411 | * @throws MorseCodeException
412 | */
413 | public function decode(string $morseCode, MorseCodeConfig $config): string
414 | {
415 | return $this->decoder->decode($morseCode, $config);
416 | }
417 | }
418 |
--------------------------------------------------------------------------------