kidscipher 0.6.0 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -37,6 +37,7 @@ The font allows you to render encoded text as graphical symbols (necesary only f
37
37
  ### Supported Ciphers
38
38
 
39
39
  - Substitution Cipher
40
+ - ✅ Letter Number Cipher (A1Z26)
40
41
  - ✅ Shift Cipher
41
42
  - ✅ Shift Alphabet
42
43
  - ✅ ABCD Shift 3-rotor
@@ -48,6 +49,7 @@ The font allows you to render encoded text as graphical symbols (necesary only f
48
49
  - ✅ Different Cross (need font)
49
50
  - ❌ Square Cross (need font)
50
51
  - ❌ Knights Templar Cross (need font)
52
+ - ❌ Musical Code [Musical code paper](https://lair.etamu.edu/cgi/viewcontent.cgi?article=1032&context=honorstheses), [scout wiki](https://scoutwiki.scouts.org.za/wiki/Codes_and_Ciphers)
51
53
  - ✅ Mobile
52
54
  - ✅ Chess
53
55
  - ✅ Cipher table - ADFGVX/ADFGX
package/dist/index.cjs.js CHANGED
@@ -484,6 +484,30 @@ const processingPipeline = (text, processors) => {
484
484
  };
485
485
 
486
486
  class Cipher {
487
+ /**
488
+ * Tokenize input using a dictionary of valid tokens.
489
+ * Longest tokens win.
490
+ */
491
+ tokenize(input, tokens) {
492
+ const result = [];
493
+ let i = 0;
494
+ while (i < input.length) {
495
+ let matched = false;
496
+ for (const token of tokens) {
497
+ if (input.startsWith(token, i)) {
498
+ result.push(token);
499
+ i += token.length;
500
+ matched = true;
501
+ break;
502
+ }
503
+ }
504
+ if (!matched) {
505
+ result.push(input[i]);
506
+ i++;
507
+ }
508
+ }
509
+ return result;
510
+ }
487
511
  encode(input, configuration, opts) {
488
512
  const { caseSensitive = false, normalizeDiacritics = true, letterSeparator: inputLetterSeparator = '', wordSeparator: inputWordSeparator = '///', } = opts?.input || {};
489
513
  const { casing = 'original', letterSeparator: outputLetterSeparator = '', wordSeparator: outputWordSeparator = ' ', } = opts?.output || {};
@@ -493,16 +517,19 @@ class Cipher {
493
517
  // normalize input into words and letters
494
518
  const words = preprocessedInput.split(inputWordSeparator);
495
519
  const encodedWords = words.map((word) => {
496
- const letters = inputLetterSeparator
497
- ? word.split(inputLetterSeparator)
498
- : word.split('');
499
- return letters
500
- .map((symbol) => {
501
- if (/\s/.test(symbol)) {
502
- return symbol;
520
+ const normalizedWord = caseSensitive ? word : word.toUpperCase();
521
+ // tokenize the word
522
+ // eg. [".-", "-...", ...] or ["A", "B", "CH", ...]
523
+ const tokens = inputLetterSeparator
524
+ ? normalizedWord.split(inputLetterSeparator)
525
+ : this.tokenize(normalizedWord, this.getEncodeTokens());
526
+ // console.log('Tokenized letters:', tokens);
527
+ return tokens
528
+ .map((token) => {
529
+ if (/\s/.test(token)) {
530
+ return token;
503
531
  }
504
- const c = caseSensitive ? symbol : symbol.toUpperCase();
505
- let encoded = this.encodeToken(c, configuration);
532
+ let encoded = this.encodeToken(token, configuration);
506
533
  if (casing === 'upper')
507
534
  encoded = encoded.toUpperCase();
508
535
  if (casing === 'lower')
@@ -546,7 +573,15 @@ class SubstitutionCipher extends Cipher {
546
573
  constructor(encodeMap) {
547
574
  super();
548
575
  this.encodeMap = encodeMap;
549
- this.decodeMap = Object.entries(encodeMap).reduce((acc, [key, value]) => ({ ...acc, [value]: key }), {});
576
+ this.decodeMap = Object.fromEntries(Object.entries(encodeMap).map(([k, v]) => [v, k]));
577
+ this.encodeTokens = Object.keys(this.encodeMap).sort((a, b) => b.length - a.length);
578
+ this.decodeTokens = Object.keys(this.decodeMap).sort((a, b) => b.length - a.length);
579
+ }
580
+ getEncodeTokens() {
581
+ return this.encodeTokens;
582
+ }
583
+ getDecodeTokens() {
584
+ return this.decodeTokens;
550
585
  }
551
586
  encodeToken(token) {
552
587
  return this.encodeMap[token] ?? '';
@@ -577,9 +612,70 @@ function withDefaultCipherOptions(opts, defaults) {
577
612
  };
578
613
  }
579
614
 
615
+ const INTERNATIONAL_MORSE = {
616
+ A: '.-',
617
+ B: '-...',
618
+ C: '-.-.',
619
+ D: '-..',
620
+ E: '.',
621
+ F: '..-.',
622
+ G: '--.',
623
+ H: '....',
624
+ I: '..',
625
+ J: '.---',
626
+ K: '-.-',
627
+ L: '.-..',
628
+ M: '--',
629
+ N: '-.',
630
+ O: '---',
631
+ P: '.--.',
632
+ Q: '--.-',
633
+ R: '.-.',
634
+ S: '...',
635
+ T: '-',
636
+ U: '..-',
637
+ V: '...-',
638
+ W: '.--',
639
+ X: '-..-',
640
+ Y: '-.--',
641
+ Z: '--..',
642
+ '0': '-----',
643
+ '1': '.----',
644
+ '2': '..---',
645
+ '3': '...--',
646
+ '4': '....-',
647
+ '5': '.....',
648
+ '6': '-....',
649
+ '7': '--...',
650
+ '8': '---..',
651
+ '9': '----.',
652
+ };
653
+ const CZECH_MORSE = {
654
+ CH: '----',
655
+ };
656
+ const GERMAN_MORSE = {
657
+ Ä: '.-.-',
658
+ Ö: '---.',
659
+ Ü: '..--',
660
+ ß: '...--..',
661
+ };
662
+ const SPANISH_MORSE = {
663
+ Ñ: '--.--',
664
+ };
665
+ const MORSE_CODE_ALPHABETS = {
666
+ intl: INTERNATIONAL_MORSE,
667
+ cs: { ...INTERNATIONAL_MORSE, ...CZECH_MORSE },
668
+ de: { ...INTERNATIONAL_MORSE, ...GERMAN_MORSE },
669
+ es: { ...INTERNATIONAL_MORSE, ...SPANISH_MORSE },
670
+ };
671
+
580
672
  class MorseCodeCipher extends SubstitutionCipher {
581
- constructor() {
582
- super(MorseCodeCipher.MORSE_CODE_MAP);
673
+ constructor({ alphabetVariant = 'intl', dotDashMapping = { dot: '.', dash: '-' }, } = {}) {
674
+ super(MorseCodeCipher.ALPHABETS[alphabetVariant ?? 'intl']);
675
+ this.dotDashMapping = {
676
+ dot: dotDashMapping.dot ?? '.',
677
+ dash: dotDashMapping.dash ?? '-',
678
+ };
583
679
  }
584
680
  encode(input, configuration, opts) {
585
681
  const { dotDashMapping = { dot: '.', dash: '-' } } = configuration || {};
@@ -601,9 +697,9 @@ class MorseCodeCipher extends SubstitutionCipher {
601
697
  .split('')
602
698
  .map((char) => {
603
699
  if (char === '.')
604
- return dotDashMapping.dot;
700
+ return this.dotDashMapping.dot;
605
701
  if (char === '-')
606
- return dotDashMapping.dash;
702
+ return this.dotDashMapping.dash;
607
703
  return char;
608
704
  })
609
705
  .join('');
@@ -626,9 +722,9 @@ class MorseCodeCipher extends SubstitutionCipher {
626
722
  normalized = input
627
723
  .split('')
628
724
  .map((char) => {
629
- if (char === dotDashMapping.dot)
725
+ if (char === this.dotDashMapping.dot)
630
726
  return '.';
631
- if (char === dotDashMapping.dash)
727
+ if (char === this.dotDashMapping.dash)
632
728
  return '-';
633
729
  return char;
634
730
  })
@@ -637,44 +733,7 @@ class MorseCodeCipher extends SubstitutionCipher {
637
733
  return super.decode(normalized, configuration, mergedOpts);
638
734
  }
639
735
  }
640
- MorseCodeCipher.MORSE_CODE_MAP = {
641
- A: '.-',
642
- B: '-...',
643
- C: '-.-.',
644
- D: '-..',
645
- E: '.',
646
- F: '..-.',
647
- G: '--.',
648
- H: '....',
649
- I: '..',
650
- J: '.---',
651
- K: '-.-',
652
- L: '.-..',
653
- M: '--',
654
- N: '-.',
655
- O: '---',
656
- P: '.--.',
657
- Q: '--.-',
658
- R: '.-.',
659
- S: '...',
660
- T: '-',
661
- U: '..-',
662
- V: '...-',
663
- W: '.--',
664
- X: '-..-',
665
- Y: '-.--',
666
- Z: '--..',
667
- '0': '-----',
668
- '1': '.----',
669
- '2': '..---',
670
- '3': '...--',
671
- '4': '....-',
672
- '5': '.....',
673
- '6': '-....',
674
- '7': '--...',
675
- '8': '---..',
676
- '9': '----.',
677
- };
736
+ MorseCodeCipher.ALPHABETS = MORSE_CODE_ALPHABETS;
678
737
 
679
738
  class MobileCipher extends SubstitutionCipher {
680
739
  constructor() {
@@ -748,22 +807,31 @@ class SubstitutionCyclicCipher extends Cipher {
748
807
  this.counters[key] = 0;
749
808
  }
750
809
  };
751
- // chech encodeMap
810
+ // normalize encode map to arrays
752
811
  this.encodeMap = Object.fromEntries(Object.entries(encodeMap).map(([key, value]) => [
753
812
  key,
754
813
  Array.isArray(value) ? value : [value],
755
814
  ]));
756
- // decode map
815
+ // build decode map
757
816
  this.decodeMap = {};
758
817
  for (const [key, values] of Object.entries(this.encodeMap)) {
759
818
  for (const val of values) {
760
819
  this.decodeMap[val] = key;
761
820
  }
762
821
  }
763
- // inialization counters
822
+ // tokenize keys (longest first)
823
+ this.encodeTokens = Object.keys(this.encodeMap).sort((a, b) => b.length - a.length);
824
+ this.decodeTokens = Object.keys(this.decodeMap).sort((a, b) => b.length - a.length);
825
+ // initialize counters
764
826
  this.counters = {};
765
827
  this.resetCounters();
766
828
  }
829
+ getEncodeTokens() {
830
+ return this.encodeTokens;
831
+ }
832
+ getDecodeTokens() {
833
+ return this.decodeTokens;
834
+ }
767
835
  encodeToken(token) {
768
836
  const options = this.encodeMap[token];
769
837
  if (!options || options.length === 0)
@@ -1039,6 +1107,36 @@ class ChineseCipher extends SubstitutionCyclicCipher {
1039
1107
  }
1040
1108
  ChineseCipher.CHINESE_MAP = ChineseCipher.generateMap();
1041
1109
 
1110
+ const ALPHABET_EN = {
1111
+ A: 'A',
1112
+ B: 'B',
1113
+ C: 'C',
1114
+ D: 'D',
1115
+ E: 'E',
1116
+ F: 'F',
1117
+ G: 'G',
1118
+ H: 'H',
1119
+ I: 'I',
1120
+ J: 'J',
1121
+ K: 'K',
1122
+ L: 'L',
1123
+ M: 'M',
1124
+ N: 'N',
1125
+ O: 'O',
1126
+ P: 'P',
1127
+ Q: 'Q',
1128
+ R: 'R',
1129
+ S: 'S',
1130
+ T: 'T',
1131
+ U: 'U',
1132
+ V: 'V',
1133
+ W: 'W',
1134
+ X: 'X',
1135
+ Y: 'Y',
1136
+ Z: 'Z',
1137
+ };
1138
+ const ALPHABET_EN_ARRAY = Object.values(ALPHABET_EN);
1139
+
1042
1140
  class ShiftCipher extends SubstitutionCipher {
1043
1141
  constructor(alphabet, shift, inputMode = 'letter', outputMode = 'letter') {
1044
1142
  let encodeMap = {};
@@ -1087,37 +1185,23 @@ class ShiftCipher extends SubstitutionCipher {
1087
1185
 
1088
1186
  class ShiftAlphabetCipher extends ShiftCipher {
1089
1187
  constructor(shift = 1) {
1090
- super(ShiftAlphabetCipher.DEFAULT_ALPHABET, shift);
1188
+ super(ALPHABET_EN_ARRAY, shift);
1091
1189
  }
1092
1190
  }
1093
- ShiftAlphabetCipher.DEFAULT_ALPHABET = [
1094
- 'A',
1095
- 'B',
1096
- 'C',
1097
- 'D',
1098
- 'E',
1099
- 'F',
1100
- 'G',
1101
- 'H',
1102
- 'I',
1103
- 'J',
1104
- 'K',
1105
- 'L',
1106
- 'M',
1107
- 'N',
1108
- 'O',
1109
- 'P',
1110
- 'Q',
1111
- 'R',
1112
- 'S',
1113
- 'T',
1114
- 'U',
1115
- 'V',
1116
- 'W',
1117
- 'X',
1118
- 'Y',
1119
- 'Z',
1120
- ];
1191
+
1192
+ const ALPHABET_NUMBERS = {
1193
+ '0': '0',
1194
+ '1': '1',
1195
+ '2': '2',
1196
+ '3': '3',
1197
+ '4': '4',
1198
+ '5': '5',
1199
+ '6': '6',
1200
+ '7': '7',
1201
+ '8': '8',
1202
+ '9': '9',
1203
+ };
1204
+ const ALPHABET_NUMBERS_ARRAY = Object.values(ALPHABET_NUMBERS);
1121
1205
 
1122
1206
  class ShiftRotorCipher extends SubstitutionCipher {
1123
1207
  constructor(baseAlphabet, rotors, shifts) {
@@ -1176,44 +1260,8 @@ class ShiftRotorABCDCipher extends ShiftRotorCipher {
1176
1260
  super(baseAlphabet, rotors, shifts);
1177
1261
  }
1178
1262
  }
1179
- ShiftRotorABCDCipher.BASE_ALPHABET = [
1180
- 'A',
1181
- 'B',
1182
- 'C',
1183
- 'D',
1184
- 'E',
1185
- 'F',
1186
- 'G',
1187
- 'H',
1188
- 'I',
1189
- 'J',
1190
- 'K',
1191
- 'L',
1192
- 'M',
1193
- 'N',
1194
- 'O',
1195
- 'P',
1196
- 'Q',
1197
- 'R',
1198
- 'S',
1199
- 'T',
1200
- 'U',
1201
- 'V',
1202
- 'W',
1203
- 'X',
1204
- 'Y',
1205
- 'Z',
1206
- '0',
1207
- '1',
1208
- '2',
1209
- '3',
1210
- '4',
1211
- '5',
1212
- '6',
1213
- '7',
1214
- '8',
1215
- '9',
1216
- ];
1263
+ // base alphabet: A-Z + 0-9
1264
+ ShiftRotorABCDCipher.BASE_ALPHABET = [...ALPHABET_EN_ARRAY, ...ALPHABET_NUMBERS_ARRAY];
1217
1265
  ShiftRotorABCDCipher.REPEAT_ALPHABET = ['A', 'B', 'C', 'D'];
1218
1266
  ShiftRotorABCDCipher.generateRepeatRotorAlphabet = (repeatAlphabet, repeat) => {
1219
1267
  let output = [];
@@ -1329,6 +1377,7 @@ class ChessCipher extends Substitution2DCipher {
1329
1377
  super(encodeAlphabet2D, horizontalKey, verticalKey);
1330
1378
  }
1331
1379
  }
1380
+ // base alphabet: A-Z + 0-9
1332
1381
  ChessCipher.BASE_ALPHABET = [
1333
1382
  'A',
1334
1383
  'B',
@@ -1368,12 +1417,77 @@ ChessCipher.BASE_ALPHABET = [
1368
1417
  '9',
1369
1418
  ];
1370
1419
 
1420
+ class LetterNumberCipher extends SubstitutionCipher {
1421
+ constructor() {
1422
+ super(LetterNumberCipher.ALPHABET);
1423
+ }
1424
+ encode(input, configuration, opts) {
1425
+ const mergedOpts = withDefaultCipherOptions(opts, {
1426
+ input: {
1427
+ caseSensitive: false,
1428
+ letterSeparator: '',
1429
+ wordSeparator: ' ',
1430
+ },
1431
+ output: {
1432
+ casing: 'original',
1433
+ letterSeparator: '-',
1434
+ wordSeparator: '---',
1435
+ },
1436
+ });
1437
+ return super.encode(input, configuration, mergedOpts);
1438
+ }
1439
+ decode(input, configuration, opts) {
1440
+ const mergedOpts = withDefaultCipherOptions(opts, {
1441
+ input: {
1442
+ caseSensitive: false,
1443
+ letterSeparator: '-',
1444
+ wordSeparator: '---',
1445
+ },
1446
+ output: {
1447
+ casing: 'lower',
1448
+ letterSeparator: '',
1449
+ wordSeparator: ' ',
1450
+ },
1451
+ });
1452
+ return super.decode(input, configuration, mergedOpts);
1453
+ }
1454
+ }
1455
+ LetterNumberCipher.ALPHABET = {
1456
+ A: '1',
1457
+ B: '2',
1458
+ C: '3',
1459
+ D: '4',
1460
+ E: '5',
1461
+ F: '6',
1462
+ G: '7',
1463
+ H: '8',
1464
+ I: '9',
1465
+ J: '10',
1466
+ K: '11',
1467
+ L: '12',
1468
+ M: '13',
1469
+ N: '14',
1470
+ O: '15',
1471
+ P: '16',
1472
+ Q: '17',
1473
+ R: '18',
1474
+ S: '19',
1475
+ T: '20',
1476
+ U: '21',
1477
+ V: '22',
1478
+ W: '23',
1479
+ X: '24',
1480
+ Y: '25',
1481
+ Z: '26',
1482
+ };
1483
+
1371
1484
  exports.ChessCipher = ChessCipher;
1372
1485
  exports.ChineseCipher = ChineseCipher;
1373
1486
  exports.Cipher = Cipher;
1374
1487
  exports.DifferentCrossCipher = DifferentCrossCipher;
1375
1488
  exports.FractionCipher = FractionCipher;
1376
1489
  exports.HebrewCrossCipher = HebrewCrossCipher;
1490
+ exports.LetterNumberCipher = LetterNumberCipher;
1377
1491
  exports.MobileCipher = MobileCipher;
1378
1492
  exports.MorseCodeCipher = MorseCodeCipher;
1379
1493
  exports.PolandCrossCipher = PolandCrossCipher;
package/dist/index.esm.js CHANGED
@@ -482,6 +482,30 @@ const processingPipeline = (text, processors) => {
482
482
  };
483
483
 
484
484
  class Cipher {
485
+ /**
486
+ * Tokenize input using a dictionary of valid tokens.
487
+ * Longest tokens win.
488
+ */
489
+ tokenize(input, tokens) {
490
+ const result = [];
491
+ let i = 0;
492
+ while (i < input.length) {
493
+ let matched = false;
494
+ for (const token of tokens) {
495
+ if (input.startsWith(token, i)) {
496
+ result.push(token);
497
+ i += token.length;
498
+ matched = true;
499
+ break;
500
+ }
501
+ }
502
+ if (!matched) {
503
+ result.push(input[i]);
504
+ i++;
505
+ }
506
+ }
507
+ return result;
508
+ }
485
509
  encode(input, configuration, opts) {
486
510
  const { caseSensitive = false, normalizeDiacritics = true, letterSeparator: inputLetterSeparator = '', wordSeparator: inputWordSeparator = '///', } = opts?.input || {};
487
511
  const { casing = 'original', letterSeparator: outputLetterSeparator = '', wordSeparator: outputWordSeparator = ' ', } = opts?.output || {};
@@ -491,16 +515,19 @@ class Cipher {
491
515
  // normalize input into words and letters
492
516
  const words = preprocessedInput.split(inputWordSeparator);
493
517
  const encodedWords = words.map((word) => {
494
- const letters = inputLetterSeparator
495
- ? word.split(inputLetterSeparator)
496
- : word.split('');
497
- return letters
498
- .map((symbol) => {
499
- if (/\s/.test(symbol)) {
500
- return symbol;
518
+ const normalizedWord = caseSensitive ? word : word.toUpperCase();
519
+ // tokenize the word
520
+ // eg. [".-", "-...", ...] or ["A", "B", "CH", ...]
521
+ const tokens = inputLetterSeparator
522
+ ? normalizedWord.split(inputLetterSeparator)
523
+ : this.tokenize(normalizedWord, this.getEncodeTokens());
524
+ // console.log('Tokenized letters:', tokens);
525
+ return tokens
526
+ .map((token) => {
527
+ if (/\s/.test(token)) {
528
+ return token;
501
529
  }
502
- const c = caseSensitive ? symbol : symbol.toUpperCase();
503
- let encoded = this.encodeToken(c, configuration);
530
+ let encoded = this.encodeToken(token, configuration);
504
531
  if (casing === 'upper')
505
532
  encoded = encoded.toUpperCase();
506
533
  if (casing === 'lower')
@@ -544,7 +571,15 @@ class SubstitutionCipher extends Cipher {
544
571
  constructor(encodeMap) {
545
572
  super();
546
573
  this.encodeMap = encodeMap;
547
- this.decodeMap = Object.entries(encodeMap).reduce((acc, [key, value]) => ({ ...acc, [value]: key }), {});
574
+ this.decodeMap = Object.fromEntries(Object.entries(encodeMap).map(([k, v]) => [v, k]));
575
+ this.encodeTokens = Object.keys(this.encodeMap).sort((a, b) => b.length - a.length);
576
+ this.decodeTokens = Object.keys(this.decodeMap).sort((a, b) => b.length - a.length);
577
+ }
578
+ getEncodeTokens() {
579
+ return this.encodeTokens;
580
+ }
581
+ getDecodeTokens() {
582
+ return this.decodeTokens;
548
583
  }
549
584
  encodeToken(token) {
550
585
  return this.encodeMap[token] ?? '';
@@ -575,9 +610,70 @@ function withDefaultCipherOptions(opts, defaults) {
575
610
  };
576
611
  }
577
612
 
613
+ const INTERNATIONAL_MORSE = {
614
+ A: '.-',
615
+ B: '-...',
616
+ C: '-.-.',
617
+ D: '-..',
618
+ E: '.',
619
+ F: '..-.',
620
+ G: '--.',
621
+ H: '....',
622
+ I: '..',
623
+ J: '.---',
624
+ K: '-.-',
625
+ L: '.-..',
626
+ M: '--',
627
+ N: '-.',
628
+ O: '---',
629
+ P: '.--.',
630
+ Q: '--.-',
631
+ R: '.-.',
632
+ S: '...',
633
+ T: '-',
634
+ U: '..-',
635
+ V: '...-',
636
+ W: '.--',
637
+ X: '-..-',
638
+ Y: '-.--',
639
+ Z: '--..',
640
+ '0': '-----',
641
+ '1': '.----',
642
+ '2': '..---',
643
+ '3': '...--',
644
+ '4': '....-',
645
+ '5': '.....',
646
+ '6': '-....',
647
+ '7': '--...',
648
+ '8': '---..',
649
+ '9': '----.',
650
+ };
651
+ const CZECH_MORSE = {
652
+ CH: '----',
653
+ };
654
+ const GERMAN_MORSE = {
655
+ Ä: '.-.-',
656
+ Ö: '---.',
657
+ Ü: '..--',
658
+ ß: '...--..',
659
+ };
660
+ const SPANISH_MORSE = {
661
+ Ñ: '--.--',
662
+ };
663
+ const MORSE_CODE_ALPHABETS = {
664
+ intl: INTERNATIONAL_MORSE,
665
+ cs: { ...INTERNATIONAL_MORSE, ...CZECH_MORSE },
666
+ de: { ...INTERNATIONAL_MORSE, ...GERMAN_MORSE },
667
+ es: { ...INTERNATIONAL_MORSE, ...SPANISH_MORSE },
668
+ };
669
+
578
670
  class MorseCodeCipher extends SubstitutionCipher {
579
- constructor() {
580
- super(MorseCodeCipher.MORSE_CODE_MAP);
671
+ constructor({ alphabetVariant = 'intl', dotDashMapping = { dot: '.', dash: '-' }, } = {}) {
672
+ super(MorseCodeCipher.ALPHABETS[alphabetVariant ?? 'intl']);
673
+ this.dotDashMapping = {
674
+ dot: dotDashMapping.dot ?? '.',
675
+ dash: dotDashMapping.dash ?? '-',
676
+ };
581
677
  }
582
678
  encode(input, configuration, opts) {
583
679
  const { dotDashMapping = { dot: '.', dash: '-' } } = configuration || {};
@@ -599,9 +695,9 @@ class MorseCodeCipher extends SubstitutionCipher {
599
695
  .split('')
600
696
  .map((char) => {
601
697
  if (char === '.')
602
- return dotDashMapping.dot;
698
+ return this.dotDashMapping.dot;
603
699
  if (char === '-')
604
- return dotDashMapping.dash;
700
+ return this.dotDashMapping.dash;
605
701
  return char;
606
702
  })
607
703
  .join('');
@@ -624,9 +720,9 @@ class MorseCodeCipher extends SubstitutionCipher {
624
720
  normalized = input
625
721
  .split('')
626
722
  .map((char) => {
627
- if (char === dotDashMapping.dot)
723
+ if (char === this.dotDashMapping.dot)
628
724
  return '.';
629
- if (char === dotDashMapping.dash)
725
+ if (char === this.dotDashMapping.dash)
630
726
  return '-';
631
727
  return char;
632
728
  })
@@ -635,44 +731,7 @@ class MorseCodeCipher extends SubstitutionCipher {
635
731
  return super.decode(normalized, configuration, mergedOpts);
636
732
  }
637
733
  }
638
- MorseCodeCipher.MORSE_CODE_MAP = {
639
- A: '.-',
640
- B: '-...',
641
- C: '-.-.',
642
- D: '-..',
643
- E: '.',
644
- F: '..-.',
645
- G: '--.',
646
- H: '....',
647
- I: '..',
648
- J: '.---',
649
- K: '-.-',
650
- L: '.-..',
651
- M: '--',
652
- N: '-.',
653
- O: '---',
654
- P: '.--.',
655
- Q: '--.-',
656
- R: '.-.',
657
- S: '...',
658
- T: '-',
659
- U: '..-',
660
- V: '...-',
661
- W: '.--',
662
- X: '-..-',
663
- Y: '-.--',
664
- Z: '--..',
665
- '0': '-----',
666
- '1': '.----',
667
- '2': '..---',
668
- '3': '...--',
669
- '4': '....-',
670
- '5': '.....',
671
- '6': '-....',
672
- '7': '--...',
673
- '8': '---..',
674
- '9': '----.',
675
- };
734
+ MorseCodeCipher.ALPHABETS = MORSE_CODE_ALPHABETS;
676
735
 
677
736
  class MobileCipher extends SubstitutionCipher {
678
737
  constructor() {
@@ -746,22 +805,31 @@ class SubstitutionCyclicCipher extends Cipher {
746
805
  this.counters[key] = 0;
747
806
  }
748
807
  };
749
- // chech encodeMap
808
+ // normalize encode map to arrays
750
809
  this.encodeMap = Object.fromEntries(Object.entries(encodeMap).map(([key, value]) => [
751
810
  key,
752
811
  Array.isArray(value) ? value : [value],
753
812
  ]));
754
- // decode map
813
+ // build decode map
755
814
  this.decodeMap = {};
756
815
  for (const [key, values] of Object.entries(this.encodeMap)) {
757
816
  for (const val of values) {
758
817
  this.decodeMap[val] = key;
759
818
  }
760
819
  }
761
- // inialization counters
820
+ // tokenize keys (longest first)
821
+ this.encodeTokens = Object.keys(this.encodeMap).sort((a, b) => b.length - a.length);
822
+ this.decodeTokens = Object.keys(this.decodeMap).sort((a, b) => b.length - a.length);
823
+ // initialize counters
762
824
  this.counters = {};
763
825
  this.resetCounters();
764
826
  }
827
+ getEncodeTokens() {
828
+ return this.encodeTokens;
829
+ }
830
+ getDecodeTokens() {
831
+ return this.decodeTokens;
832
+ }
765
833
  encodeToken(token) {
766
834
  const options = this.encodeMap[token];
767
835
  if (!options || options.length === 0)
@@ -1037,6 +1105,36 @@ class ChineseCipher extends SubstitutionCyclicCipher {
1037
1105
  }
1038
1106
  ChineseCipher.CHINESE_MAP = ChineseCipher.generateMap();
1039
1107
 
1108
+ const ALPHABET_EN = {
1109
+ A: 'A',
1110
+ B: 'B',
1111
+ C: 'C',
1112
+ D: 'D',
1113
+ E: 'E',
1114
+ F: 'F',
1115
+ G: 'G',
1116
+ H: 'H',
1117
+ I: 'I',
1118
+ J: 'J',
1119
+ K: 'K',
1120
+ L: 'L',
1121
+ M: 'M',
1122
+ N: 'N',
1123
+ O: 'O',
1124
+ P: 'P',
1125
+ Q: 'Q',
1126
+ R: 'R',
1127
+ S: 'S',
1128
+ T: 'T',
1129
+ U: 'U',
1130
+ V: 'V',
1131
+ W: 'W',
1132
+ X: 'X',
1133
+ Y: 'Y',
1134
+ Z: 'Z',
1135
+ };
1136
+ const ALPHABET_EN_ARRAY = Object.values(ALPHABET_EN);
1137
+
1040
1138
  class ShiftCipher extends SubstitutionCipher {
1041
1139
  constructor(alphabet, shift, inputMode = 'letter', outputMode = 'letter') {
1042
1140
  let encodeMap = {};
@@ -1085,37 +1183,23 @@ class ShiftCipher extends SubstitutionCipher {
1085
1183
 
1086
1184
  class ShiftAlphabetCipher extends ShiftCipher {
1087
1185
  constructor(shift = 1) {
1088
- super(ShiftAlphabetCipher.DEFAULT_ALPHABET, shift);
1186
+ super(ALPHABET_EN_ARRAY, shift);
1089
1187
  }
1090
1188
  }
1091
- ShiftAlphabetCipher.DEFAULT_ALPHABET = [
1092
- 'A',
1093
- 'B',
1094
- 'C',
1095
- 'D',
1096
- 'E',
1097
- 'F',
1098
- 'G',
1099
- 'H',
1100
- 'I',
1101
- 'J',
1102
- 'K',
1103
- 'L',
1104
- 'M',
1105
- 'N',
1106
- 'O',
1107
- 'P',
1108
- 'Q',
1109
- 'R',
1110
- 'S',
1111
- 'T',
1112
- 'U',
1113
- 'V',
1114
- 'W',
1115
- 'X',
1116
- 'Y',
1117
- 'Z',
1118
- ];
1189
+
1190
+ const ALPHABET_NUMBERS = {
1191
+ '0': '0',
1192
+ '1': '1',
1193
+ '2': '2',
1194
+ '3': '3',
1195
+ '4': '4',
1196
+ '5': '5',
1197
+ '6': '6',
1198
+ '7': '7',
1199
+ '8': '8',
1200
+ '9': '9',
1201
+ };
1202
+ const ALPHABET_NUMBERS_ARRAY = Object.values(ALPHABET_NUMBERS);
1119
1203
 
1120
1204
  class ShiftRotorCipher extends SubstitutionCipher {
1121
1205
  constructor(baseAlphabet, rotors, shifts) {
@@ -1174,44 +1258,8 @@ class ShiftRotorABCDCipher extends ShiftRotorCipher {
1174
1258
  super(baseAlphabet, rotors, shifts);
1175
1259
  }
1176
1260
  }
1177
- ShiftRotorABCDCipher.BASE_ALPHABET = [
1178
- 'A',
1179
- 'B',
1180
- 'C',
1181
- 'D',
1182
- 'E',
1183
- 'F',
1184
- 'G',
1185
- 'H',
1186
- 'I',
1187
- 'J',
1188
- 'K',
1189
- 'L',
1190
- 'M',
1191
- 'N',
1192
- 'O',
1193
- 'P',
1194
- 'Q',
1195
- 'R',
1196
- 'S',
1197
- 'T',
1198
- 'U',
1199
- 'V',
1200
- 'W',
1201
- 'X',
1202
- 'Y',
1203
- 'Z',
1204
- '0',
1205
- '1',
1206
- '2',
1207
- '3',
1208
- '4',
1209
- '5',
1210
- '6',
1211
- '7',
1212
- '8',
1213
- '9',
1214
- ];
1261
+ // base alphabet: A-Z + 0-9
1262
+ ShiftRotorABCDCipher.BASE_ALPHABET = [...ALPHABET_EN_ARRAY, ...ALPHABET_NUMBERS_ARRAY];
1215
1263
  ShiftRotorABCDCipher.REPEAT_ALPHABET = ['A', 'B', 'C', 'D'];
1216
1264
  ShiftRotorABCDCipher.generateRepeatRotorAlphabet = (repeatAlphabet, repeat) => {
1217
1265
  let output = [];
@@ -1327,6 +1375,7 @@ class ChessCipher extends Substitution2DCipher {
1327
1375
  super(encodeAlphabet2D, horizontalKey, verticalKey);
1328
1376
  }
1329
1377
  }
1378
+ // base alphabet: A-Z + 0-9
1330
1379
  ChessCipher.BASE_ALPHABET = [
1331
1380
  'A',
1332
1381
  'B',
@@ -1366,4 +1415,68 @@ ChessCipher.BASE_ALPHABET = [
1366
1415
  '9',
1367
1416
  ];
1368
1417
 
1369
- export { ChessCipher, ChineseCipher, Cipher, DifferentCrossCipher, FractionCipher, HebrewCrossCipher, MobileCipher, MorseCodeCipher, PolandCrossCipher, ShiftAlphabetCipher, ShiftRotorABCDCipher, SmallCrossCipher, SpiderCipher, Substitution2DCipher, SubstitutionCipher, TableKeyFiveToFiveCipher };
1418
+ class LetterNumberCipher extends SubstitutionCipher {
1419
+ constructor() {
1420
+ super(LetterNumberCipher.ALPHABET);
1421
+ }
1422
+ encode(input, configuration, opts) {
1423
+ const mergedOpts = withDefaultCipherOptions(opts, {
1424
+ input: {
1425
+ caseSensitive: false,
1426
+ letterSeparator: '',
1427
+ wordSeparator: ' ',
1428
+ },
1429
+ output: {
1430
+ casing: 'original',
1431
+ letterSeparator: '-',
1432
+ wordSeparator: '---',
1433
+ },
1434
+ });
1435
+ return super.encode(input, configuration, mergedOpts);
1436
+ }
1437
+ decode(input, configuration, opts) {
1438
+ const mergedOpts = withDefaultCipherOptions(opts, {
1439
+ input: {
1440
+ caseSensitive: false,
1441
+ letterSeparator: '-',
1442
+ wordSeparator: '---',
1443
+ },
1444
+ output: {
1445
+ casing: 'lower',
1446
+ letterSeparator: '',
1447
+ wordSeparator: ' ',
1448
+ },
1449
+ });
1450
+ return super.decode(input, configuration, mergedOpts);
1451
+ }
1452
+ }
1453
+ LetterNumberCipher.ALPHABET = {
1454
+ A: '1',
1455
+ B: '2',
1456
+ C: '3',
1457
+ D: '4',
1458
+ E: '5',
1459
+ F: '6',
1460
+ G: '7',
1461
+ H: '8',
1462
+ I: '9',
1463
+ J: '10',
1464
+ K: '11',
1465
+ L: '12',
1466
+ M: '13',
1467
+ N: '14',
1468
+ O: '15',
1469
+ P: '16',
1470
+ Q: '17',
1471
+ R: '18',
1472
+ S: '19',
1473
+ T: '20',
1474
+ U: '21',
1475
+ V: '22',
1476
+ W: '23',
1477
+ X: '24',
1478
+ Y: '25',
1479
+ Z: '26',
1480
+ };
1481
+
1482
+ export { ChessCipher, ChineseCipher, Cipher, DifferentCrossCipher, FractionCipher, HebrewCrossCipher, LetterNumberCipher, MobileCipher, MorseCodeCipher, PolandCrossCipher, ShiftAlphabetCipher, ShiftRotorABCDCipher, SmallCrossCipher, SpiderCipher, Substitution2DCipher, SubstitutionCipher, TableKeyFiveToFiveCipher };
@@ -2,6 +2,13 @@ import { CipherOptions } from '../core/cipher-options/CipherOptions';
2
2
  export type CipherConfigurationsRecord = Record<string, any>;
3
3
  declare abstract class Cipher {
4
4
  abstract encodeToken(token: string, configuration?: CipherConfigurationsRecord): string;
5
+ /**
6
+ * Tokenize input using a dictionary of valid tokens.
7
+ * Longest tokens win.
8
+ */
9
+ protected tokenize(input: string, tokens: string[]): string[];
10
+ abstract getEncodeTokens(): string[];
11
+ abstract getDecodeTokens(): string[];
5
12
  encode(input: string, configuration?: CipherConfigurationsRecord, opts?: CipherOptions): string;
6
13
  abstract decodeToken(token: string, configuration?: CipherConfigurationsRecord): string;
7
14
  decode(input: string, configuration?: CipherConfigurationsRecord, opts?: CipherOptions): string;
@@ -0,0 +1,16 @@
1
+ export declare const MORSE_CODE_ALPHABETS: {
2
+ intl: Record<string, string>;
3
+ cs: {
4
+ CH: string;
5
+ };
6
+ de: {
7
+ Ä: string;
8
+ Ö: string;
9
+ Ü: string;
10
+ ß: string;
11
+ };
12
+ es: {
13
+ Ñ: string;
14
+ };
15
+ };
16
+ export type MorseCodeAlphabetKey = keyof typeof MORSE_CODE_ALPHABETS;
@@ -1,16 +1,34 @@
1
1
  import SubstitutionCipher from '../substitution/SubstitutionCipher';
2
2
  import { CipherOptions } from '../../core/cipher-options/CipherOptions';
3
3
  import { CipherConfigurationsRecord } from '../Cipher';
4
- export type MorseCodeCipherOptions = CipherConfigurationsRecord | {
5
- dotDashMapping?: {
6
- dot?: string;
7
- dash?: string;
8
- };
4
+ import { MorseCodeAlphabetKey } from './MorseCodeAlphabet';
5
+ type DotDashMapping = {
6
+ dot: string;
7
+ dash: string;
8
+ };
9
+ type MorseCodeCipherProps = {
10
+ alphabetVariant?: MorseCodeAlphabetKey;
11
+ dotDashMapping?: Partial<DotDashMapping>;
9
12
  };
10
13
  declare class MorseCodeCipher extends SubstitutionCipher {
11
- static MORSE_CODE_MAP: Record<string, string>;
12
- constructor();
13
- encode(input: string, configuration?: MorseCodeCipherOptions, opts?: CipherOptions): string;
14
- decode(input: string, configuration?: MorseCodeCipherOptions, opts?: CipherOptions): string;
14
+ private readonly dotDashMapping;
15
+ static readonly ALPHABETS: {
16
+ intl: Record<string, string>;
17
+ cs: {
18
+ CH: string;
19
+ };
20
+ de: {
21
+ Ä: string;
22
+ Ö: string;
23
+ Ü: string;
24
+ ß: string;
25
+ };
26
+ es: {
27
+ Ñ: string;
28
+ };
29
+ };
30
+ constructor({ alphabetVariant, dotDashMapping, }?: MorseCodeCipherProps);
31
+ encode(input: string, configuration?: CipherConfigurationsRecord, opts?: CipherOptions): string;
32
+ decode(input: string, configuration?: CipherConfigurationsRecord, opts?: CipherOptions): string;
15
33
  }
16
34
  export default MorseCodeCipher;
@@ -0,0 +1,11 @@
1
+ import SubstitutionCipher from '../substitution/SubstitutionCipher';
2
+ import { CipherOptions } from '../../core/cipher-options/CipherOptions';
3
+ import { CipherConfigurationsRecord } from '../Cipher';
4
+ export type LetterNumberCipherOptions = CipherConfigurationsRecord;
5
+ declare class LetterNumberCipher extends SubstitutionCipher {
6
+ static readonly ALPHABET: Record<string, string>;
7
+ constructor();
8
+ encode(input: string, configuration?: LetterNumberCipherOptions, opts?: CipherOptions): string;
9
+ decode(input: string, configuration?: LetterNumberCipherOptions, opts?: CipherOptions): string;
10
+ }
11
+ export default LetterNumberCipher;
@@ -1,6 +1,5 @@
1
1
  import ShiftCipher from './ShiftCipher';
2
2
  declare class ShiftAlphabetCipher extends ShiftCipher {
3
- static DEFAULT_ALPHABET: string[];
4
3
  constructor(shift?: number);
5
4
  }
6
5
  export default ShiftAlphabetCipher;
@@ -2,7 +2,11 @@ import Cipher from '../Cipher';
2
2
  declare class SubstitutionCipher extends Cipher {
3
3
  protected encodeMap: Record<string, string>;
4
4
  protected decodeMap: Record<string, string>;
5
+ protected encodeTokens: string[];
6
+ protected decodeTokens: string[];
5
7
  constructor(encodeMap: Record<string, string>);
8
+ getEncodeTokens(): string[];
9
+ getDecodeTokens(): string[];
6
10
  encodeToken(token: string): string;
7
11
  decodeToken(token: string): string;
8
12
  }
@@ -4,7 +4,11 @@ declare class SubstitutionCyclicCipher extends Cipher {
4
4
  protected encodeMap: Record<string, string[]>;
5
5
  protected decodeMap: Record<string, string>;
6
6
  protected counters: Record<string, number>;
7
+ protected encodeTokens: string[];
8
+ protected decodeTokens: string[];
7
9
  constructor(encodeMap: Record<string, string | string[]>);
10
+ getEncodeTokens(): string[];
11
+ getDecodeTokens(): string[];
8
12
  resetCounters: () => void;
9
13
  encodeToken(token: string): string;
10
14
  encode(input: string, configuration?: CipherConfigurationsRecord, opts?: CipherOptions): string;
@@ -0,0 +1,47 @@
1
+ export declare const ALPHABET_CZ: {
2
+ readonly A: "A";
3
+ readonly Á: "Á";
4
+ readonly B: "B";
5
+ readonly C: "C";
6
+ readonly Č: "Č";
7
+ readonly D: "D";
8
+ readonly Ď: "Ď";
9
+ readonly E: "E";
10
+ readonly É: "É";
11
+ readonly Ě: "Ě";
12
+ readonly F: "F";
13
+ readonly G: "G";
14
+ readonly H: "H";
15
+ readonly CH: "CH";
16
+ readonly I: "I";
17
+ readonly Í: "Í";
18
+ readonly J: "J";
19
+ readonly K: "K";
20
+ readonly L: "L";
21
+ readonly M: "M";
22
+ readonly N: "N";
23
+ readonly Ň: "Ň";
24
+ readonly O: "O";
25
+ readonly Ó: "Ó";
26
+ readonly P: "P";
27
+ readonly Q: "Q";
28
+ readonly R: "R";
29
+ readonly Ř: "Ř";
30
+ readonly S: "S";
31
+ readonly Š: "Š";
32
+ readonly T: "T";
33
+ readonly Ť: "Ť";
34
+ readonly U: "U";
35
+ readonly Ú: "Ú";
36
+ readonly Ů: "Ů";
37
+ readonly V: "V";
38
+ readonly W: "W";
39
+ readonly X: "X";
40
+ readonly Y: "Y";
41
+ readonly Ý: "Ý";
42
+ readonly Z: "Z";
43
+ readonly Ž: "Ž";
44
+ };
45
+ export type AlphabetCz = (typeof ALPHABET_CZ)[keyof typeof ALPHABET_CZ];
46
+ export declare const ALPHABET_CZ_ARRAY: AlphabetCz[];
47
+ export declare const ALPHABET_CZ_NO_PUNCTUATION_ARRAY: AlphabetCz[];
@@ -0,0 +1,30 @@
1
+ export declare const ALPHABET_EN: {
2
+ readonly A: "A";
3
+ readonly B: "B";
4
+ readonly C: "C";
5
+ readonly D: "D";
6
+ readonly E: "E";
7
+ readonly F: "F";
8
+ readonly G: "G";
9
+ readonly H: "H";
10
+ readonly I: "I";
11
+ readonly J: "J";
12
+ readonly K: "K";
13
+ readonly L: "L";
14
+ readonly M: "M";
15
+ readonly N: "N";
16
+ readonly O: "O";
17
+ readonly P: "P";
18
+ readonly Q: "Q";
19
+ readonly R: "R";
20
+ readonly S: "S";
21
+ readonly T: "T";
22
+ readonly U: "U";
23
+ readonly V: "V";
24
+ readonly W: "W";
25
+ readonly X: "X";
26
+ readonly Y: "Y";
27
+ readonly Z: "Z";
28
+ };
29
+ export type AlphabetEn = (typeof ALPHABET_EN)[keyof typeof ALPHABET_EN];
30
+ export declare const ALPHABET_EN_ARRAY: AlphabetEn[];
@@ -0,0 +1,14 @@
1
+ export declare const ALPHABET_NUMBERS: {
2
+ '0': string;
3
+ '1': string;
4
+ '2': string;
5
+ '3': string;
6
+ '4': string;
7
+ '5': string;
8
+ '6': string;
9
+ '7': string;
10
+ '8': string;
11
+ '9': string;
12
+ };
13
+ export type AlphabetNumber = (typeof ALPHABET_NUMBERS)[keyof typeof ALPHABET_NUMBERS];
14
+ export declare const ALPHABET_NUMBERS_ARRAY: AlphabetNumber[];
@@ -15,3 +15,4 @@ export { default as ShiftRotorABCDCipher } from './cipher/shift/ShiftRotorABCDCi
15
15
  export { default as Substitution2DCipher } from './cipher/substitution/Substitution2DCipher';
16
16
  export { default as TableKeyFiveToFiveCipher } from './cipher/table/TableKeyFiveToFiveCipher';
17
17
  export { default as ChessCipher } from './cipher/table/ChessCipher';
18
+ export { default as LetterNumberCipher } from './cipher/number/LetterNumberCipher';
package/package.json CHANGED
@@ -1,8 +1,12 @@
1
1
  {
2
2
  "name": "kidscipher",
3
- "version": "0.6.0",
3
+ "version": "0.7.0",
4
4
  "license": "MIT",
5
5
  "homepage": "https://github.com/fandau1/kidscipher",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "https://github.com/fandau1/kidscipher.git"
9
+ },
6
10
  "type": "module",
7
11
  "types": "dist/types/index.d.ts",
8
12
  "exports": {