using System; using System.Collections.Generic; using System.Text; using System.Text.RegularExpressions; namespace AusPostCode { public class Barcode { private readonly struct BarcodeFragment { public EncodingFormat TypicalFormat { get; } public int Length { get;} public int Offset { get; } public BarcodeFragment(EncodingFormat typicalFormat, int length, int offset) { TypicalFormat = typicalFormat; Length = length; Offset = offset; } } private readonly Dictionary _fragments = new Dictionary { ["FormatControlCode"] = new BarcodeFragment(EncodingFormat.N, 4, 2), ["SortingCode"] = new BarcodeFragment(EncodingFormat.N, 16, 6), ["CustomerInformation2"] = new BarcodeFragment(EncodingFormat.C, 16, 22), ["CustomerInformation3"] = new BarcodeFragment(EncodingFormat.C, 31, 22), ["ErrorCorrection"] = new BarcodeFragment(EncodingFormat.BarToDecimal, 12, - 16), }; private enum EncodingFormat { N, C, BarToDecimal, } private int _formatCode; private readonly Dictionary _formatTable = new Dictionary { [0] = "Null Customer Barcode", [11] = "Standard Customer Barcode", [52] = "Customer Business Reply Paid", [59] = "Customer Barcode 2", [62] = "Customer Barcode 3", [67] = "Customer Business Reply Paid", [72] = "International Business Reply Paid", [77] = "International Business Reply Paid", }; 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(GetFragment(_fragments["FormatControlCode"])); Console.WriteLine(Format); // process Sorting Code Field SortingCode = int.Parse(GetFragment(_fragments["SortingCode"])); Console.WriteLine($"Sorting code: {SortingCode}"); // process Customer Information, if necessary // TODO: actually do this, and get a code to test it on. also, ensure that customer codes are the right length if (_formatCode == 59) { Console.WriteLine("This code has a Customer Information field, but I don't know how to process that yet 0uo"); } else if (_formatCode == 62) { Console.WriteLine("This code has a Customer Information field, but I don't know how to process that yet 0uo"); } // process Reed-Solomon Error Correction Bars // TODO: actually validate the barcode Console.WriteLine(BaseConversion.FromBase(GetFragment(_fragments["ErrorCorrection"]), 64)); } public int SortingCode { get; set; } public int CustomerInformation { get; set; } public string Format => _formatTable.ContainsKey(_formatCode) ? _formatTable[_formatCode] : $"Unknown ({_formatCode})"; private List Warnings { get; } = new List(); private string Code { get; } private string GetFragment(BarcodeFragment fragment, bool decode = true) { var data = Code.Substring(fragment.Offset < 0 ? Code.Length + fragment.Offset : fragment.Offset, fragment.Length); if (!decode) { return data; } // bool badData; var chunkLength = fragment.TypicalFormat == EncodingFormat.N ? 2 : 3; var rx = new Regex("^[0123]+$"); var sb = new StringBuilder(); if (!rx.IsMatch(data)) { throw new ArgumentException("Input length must be a quaternary number.", nameof(data)); } for (var i = 0; i < data.Length; i += chunkLength) { var chunk = data.Substring(i, chunkLength); switch (fragment.TypicalFormat) { 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 {data.ToString()}."); } else { sb.Append(BaseConversion.FromBase(chunk, 3)); } break; case EncodingFormat.C: break; case EncodingFormat.BarToDecimal: var digitInt = BaseConversion.FromBase(chunk, 4); sb.Insert(0, BaseConversion.ToBase(digitInt, 64)); break; default: throw new ArgumentOutOfRangeException(nameof(fragment.TypicalFormat), fragment.TypicalFormat, null); } } return sb.ToString(); } 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(); } } }