├── 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 | Logo 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 | --------------------------------------------------------------------------------