diff --git a/.gitignore b/.gitignore index e76c073..2f91df5 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ bin/ obj/ out/ +spec.* diff --git a/.idea/.idea.auspostcode.dir/.idea/contentModel.xml b/.idea/.idea.auspostcode.dir/.idea/contentModel.xml index c0f1a38..e07af1a 100644 --- a/.idea/.idea.auspostcode.dir/.idea/contentModel.xml +++ b/.idea/.idea.auspostcode.dir/.idea/contentModel.xml @@ -15,6 +15,7 @@ + diff --git a/.idea/.idea.auspostcode.dir/.idea/workspace.xml b/.idea/.idea.auspostcode.dir/.idea/workspace.xml index 06367db..e3bdc7c 100644 --- a/.idea/.idea.auspostcode.dir/.idea/workspace.xml +++ b/.idea/.idea.auspostcode.dir/.idea/workspace.xml @@ -3,33 +3,71 @@ auspostcode.csproj + + + + + + + + + - + + + + + + + + + + + + + + + + + + + + + + - + @@ -60,36 +98,161 @@ + + + - + - - + + - - + + - - + + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Barcode.cs b/Barcode.cs index b8785fe..57f0d87 100644 --- a/Barcode.cs +++ b/Barcode.cs @@ -1,8 +1,26 @@ +using System; using System.Collections.Generic; +using System.Text; +using System.Text.RegularExpressions; namespace AusPostCode { public class Barcode { - public Dictionary FormatTable = new Dictionary() { + public enum BarcodeFragment { + FormatControlCode, + SortingCode, + CustomerInformation, + ErrorCorrection, + } + + public enum EncodingFormat { + N, + C, + BarToDecimal, + } + + private int _formatCode; + + public Dictionary FormatTable = new Dictionary { [0] = "Null Customer Barcode", [11] = "Standard Customer Barcode", [52] = "Customer Business Reply Paid", @@ -10,14 +28,95 @@ namespace AusPostCode { [62] = "Customer Barcode 3", [67] = "Customer Business Reply Paid", [72] = "International Business Reply Paid", - [77] = "International Business Reply Paid" + [77] = "International Business Reply Paid", }; - public int SortingCode { get; set; } - public int CustomerInformation { get; set; } - public string Code { get; set; } public Barcode(string code) { Code = code; + // - check for start and end bars + if (Code.Substring(0, 2) != "13") { + Warnings.Add("Couldn't find Start Bars"); + Code = $"13{Code}"; + } + + if (Code.Substring(Code.Length - 3, 2) != "13") { + Warnings.Add("Couldn't find End Bars"); + Code = $"{Code}13"; + } + + // process Format Control Code + _formatCode = int.Parse(Decode(GetFragment(BarcodeFragment.FormatControlCode), EncodingFormat.N)); + Console.WriteLine(Format); + + // process Sorting Code Field + SortingCode = int.Parse(Decode(GetFragment(BarcodeFragment.SortingCode), EncodingFormat.N)); + Console.WriteLine($"Sorting code: {SortingCode}"); + } + + public int SortingCode { get; set; } + public int CustomerInformation { get; set; } + public string Format => FormatTable.ContainsKey(_formatCode) ? FormatTable[_formatCode] : $"Unknown ({_formatCode})"; + private List Warnings { get; set; } = new List(); + private string Code { get; set; } + + private string GetFragment(BarcodeFragment barcodeFragment) { + return barcodeFragment switch { + BarcodeFragment.FormatControlCode => Code.Substring(2, 4), + BarcodeFragment.SortingCode => Code.Substring(6, 16), + BarcodeFragment.CustomerInformation when _formatCode == 59 => Code.Substring(22, 16), + BarcodeFragment.CustomerInformation when _formatCode == 62 => Code.Substring(22, 31), + BarcodeFragment.CustomerInformation => null, // format doesn't support the customer information field + BarcodeFragment.ErrorCorrection => Code.Substring(Code.Length - 15, + 12), // the error correction bars are always immediately before the stop bars + _ => throw new ArgumentOutOfRangeException(nameof(barcodeFragment), barcodeFragment, null), + }; + } + + private static string Decode(string input, EncodingFormat format) { + // bool badData; + var chunkLength = format == EncodingFormat.N ? 2 : 3; + if (input.Length % 2 != 0) { + throw new ArgumentException($"Input length must be a multiple of {chunkLength}.", nameof(input)); + } + + var rx = new Regex("^[0123]+$"); + if (!rx.IsMatch(input)) { + throw new ArgumentException("Input length must be a quaternary number.", nameof(input)); + } + + var sb = new StringBuilder(); + for (var i = 0; i < input.Length; i += chunkLength) { + var chunk = input.Substring(i, chunkLength); + + switch (format) { + case EncodingFormat.N: + // format N supports the digits 0 through 9, and nothing else. + // digits 0 through 8 are stored as their ternary representations, while 9 is stored as "30". + + if (chunk == "30") { + sb.Append(9); + } + else if (chunk.Contains("3")) { + // not a ternary number + throw new ArgumentException($"{chunk} is not a valid identifier for format {format.ToString()}."); + } + else { + sb.Append(BaseConversion.FromBase(chunk, 3)); + } + break; + + case EncodingFormat.C: + break; + + case EncodingFormat.BarToDecimal: + break; + + default: + throw new ArgumentOutOfRangeException(nameof(format), format, null); + } + } + + return sb.ToString(); } } } diff --git a/BaseConversion.cs b/BaseConversion.cs new file mode 100644 index 0000000..bb2dbf9 --- /dev/null +++ b/BaseConversion.cs @@ -0,0 +1,70 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace AusPostCode { + public static class BaseConversion { + // honestly surprised c# doesn't do this out of the box + + private static readonly char[] Characters = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ".ToCharArray(); + + // thanks to https://stackoverflow.com/a/35004409 + private static readonly Dictionary CharacterIndices = + Characters.Select((c, i) => new {Char = c, Index = i}).ToDictionary(c => c.Char, c => c.Index); + + public static string ToBase(int num, int numberBase) { + if (numberBase > Characters.Length) { + throw new ArgumentOutOfRangeException( + nameof(numberBase), + $"Base must be below {Characters.Length}" + ); + } + + if (Math.Abs(num) == 0) { + return num.ToString(); + } + + var digits = new List(); + var negative = num < 0; + + while (num > 0) { + digits.Add(Characters[num % numberBase]); + num /= numberBase; + } + + digits.Reverse(); + return (negative ? "-" : "") + string.Join("", digits); + } + + public static int FromBase(string num, int numberBase) { + if (numberBase > Characters.Length) { + throw new ArgumentOutOfRangeException( + nameof(numberBase), + $"Base must be below {Characters.Length}" + ); + } + + var negative = false; + + if (num.StartsWith("-")) { + // it's a negative number + negative = true; + num = num.Substring(1); + } + + var x = num.ToCharArray(); + var result = 0; + + // thanks to https://stackoverflow.com/a/35004409 again for this 0uo + for (var i = 0; i < x.Length; i++) { + result += CharacterIndices[x[i]] * (int) Math.Pow(numberBase, x.Length - i - 1); + } + + if (negative) { + result *= -1; + } + + return result; + } + } +} diff --git a/Folder.DotSettings.user b/Folder.DotSettings.user deleted file mode 100644 index 72d83da..0000000 --- a/Folder.DotSettings.user +++ /dev/null @@ -1,2 +0,0 @@ - - True \ No newline at end of file diff --git a/Program.cs b/Program.cs index b99bb3e..1da76be 100644 --- a/Program.cs +++ b/Program.cs @@ -1,37 +1,36 @@ -/* -AusPostCode - A program for parsing and creating Australia Post barcodes. -Copyright (C) 2020 Lynnesbian - -This program is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with this program. If not, see . - */ - -using System; -using CommandLine; - -namespace AusPostCode { - internal class Program { - - public class Options { - [Option('d', "decode", Required = false, - HelpText = "Decode the text passed in, turning it into human readable information.")] - public bool Decode { get; set; } - } - - private static void Main(string[] args) { - Console.WriteLine("Hello World!"); - string testCode = "3111961842093257385431"; - var barcode = new Barcode(testCode); - } - } -} +/* +AusPostCode - A program for parsing and creating Australia Post barcodes. +Copyright (C) 2020 Lynnesbian + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . + */ + +using System; +using CommandLine; + +namespace AusPostCode { + internal class Program { + private static void Main(string[] args) { + Console.WriteLine("Hello World!"); + const string testCode = "1301012112011202120020300110110322113"; + var barcode = new Barcode(testCode); + } + + public class Options { + [Option('d', "decode", Required = false, + HelpText = "Decode the text passed in, turning it into human readable information.")] + public bool Decode { get; set; } + } + } +}