implemented encoding format c, and support for customer information 3. i would do 2 as well but it's 0347. next up, unit testing for the first time in my stinchin life

This commit is contained in:
Lynne Megido 2020-10-14 03:47:33 +10:00
parent d290dbadad
commit a34702dc6c
Signed by: lynnesbian
GPG Key ID: F0A184B5213D9F90
3 changed files with 161 additions and 80 deletions

View File

@ -22,7 +22,6 @@
<list default="true" id="373c5ee6-3f10-450b-8af6-fc4dd99290b0" name="Default Changelist" comment="initial commit, as they say">
<change beforePath="$PROJECT_DIR$/.idea/.idea.auspostcode.dir/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/.idea.auspostcode.dir/.idea/workspace.xml" afterDir="false" />
<change beforePath="$PROJECT_DIR$/Barcode.cs" beforeDir="false" afterPath="$PROJECT_DIR$/Barcode.cs" afterDir="false" />
<change beforePath="$PROJECT_DIR$/BaseConversion.cs" beforeDir="false" afterPath="$PROJECT_DIR$/BaseConversion.cs" afterDir="false" />
<change beforePath="$PROJECT_DIR$/Program.cs" beforeDir="false" afterPath="$PROJECT_DIR$/Program.cs" afterDir="false" />
</list>
<option name="SHOW_DIALOG" value="false" />
@ -45,6 +44,7 @@
<option value="$PROJECT_DIR$/BarcodeVersion.cs" />
<option value="$PROJECT_DIR$/.gitignore" />
<option value="$PROJECT_DIR$/BaseConversion.cs" />
<option value="$APPLICATION_CONFIG_DIR$/scratches/scratch.txt" />
<option value="$PROJECT_DIR$/Program.cs" />
<option value="$PROJECT_DIR$/Barcode.cs" />
</list>
@ -65,6 +65,7 @@
<property name="WebServerToolWindowFactoryState" value="false" />
<property name="XThreadsFramesViewSplitterKey" value="0.7161862" />
<property name="add_unversioned_files" value="true" />
<property name="com.intellij.ide.scratch.LRUPopupBuilder$1/New Scratch File" value="TEXT" />
<property name="settings.editor.selected.configurable" value="preferences.sourceCode.C#" />
<property name="vue.rearranger.settings.migration" value="true" />
</component>
@ -96,7 +97,7 @@
<option name="number" value="Default" />
<option name="presentableId" value="Default" />
<updated>1602593942454</updated>
<workItem from="1602593947835" duration="13843000" />
<workItem from="1602593947835" duration="17273000" />
</task>
<task id="LOCAL-00001" summary="initial commit, as they say">
<created>1602597326777</created>
@ -148,86 +149,86 @@
<screen x="0" y="0" width="1920" height="1054" />
</state>
<state x="727" y="366" width="424" height="478" key="FileChooserDialogImpl/0.0.1920.1054@0.0.1920.1054" timestamp="1602597073977" />
<state width="1878" height="374" key="GridCell.Tab.0.bottom" timestamp="1602607646431">
<state width="1878" height="374" key="GridCell.Tab.0.bottom" timestamp="1602610374478">
<screen x="0" y="0" width="1920" height="1054" />
</state>
<state width="1878" height="374" key="GridCell.Tab.0.bottom/0.0.1920.1054@0.0.1920.1054" timestamp="1602607646431" />
<state width="1878" height="374" key="GridCell.Tab.0.center" timestamp="1602607646431">
<state width="1878" height="374" key="GridCell.Tab.0.bottom/0.0.1920.1054@0.0.1920.1054" timestamp="1602610374478" />
<state width="1878" height="374" key="GridCell.Tab.0.center" timestamp="1602610374477">
<screen x="0" y="0" width="1920" height="1054" />
</state>
<state width="1878" height="374" key="GridCell.Tab.0.center/0.0.1920.1054@0.0.1920.1054" timestamp="1602607646431" />
<state width="1878" height="374" key="GridCell.Tab.0.left" timestamp="1602607646431">
<state width="1878" height="374" key="GridCell.Tab.0.center/0.0.1920.1054@0.0.1920.1054" timestamp="1602610374477" />
<state width="1878" height="374" key="GridCell.Tab.0.left" timestamp="1602610374476">
<screen x="0" y="0" width="1920" height="1054" />
</state>
<state width="1878" height="374" key="GridCell.Tab.0.left/0.0.1920.1054@0.0.1920.1054" timestamp="1602607646431" />
<state width="1878" height="374" key="GridCell.Tab.0.right" timestamp="1602607646431">
<state width="1878" height="374" key="GridCell.Tab.0.left/0.0.1920.1054@0.0.1920.1054" timestamp="1602610374476" />
<state width="1878" height="374" key="GridCell.Tab.0.right" timestamp="1602610374478">
<screen x="0" y="0" width="1920" height="1054" />
</state>
<state width="1878" height="374" key="GridCell.Tab.0.right/0.0.1920.1054@0.0.1920.1054" timestamp="1602607646431" />
<state width="1878" height="374" key="GridCell.Tab.1.bottom" timestamp="1602607646287">
<state width="1878" height="374" key="GridCell.Tab.0.right/0.0.1920.1054@0.0.1920.1054" timestamp="1602610374478" />
<state width="1878" height="374" key="GridCell.Tab.1.bottom" timestamp="1602610374479">
<screen x="0" y="0" width="1920" height="1054" />
</state>
<state width="1878" height="374" key="GridCell.Tab.1.bottom/0.0.1920.1054@0.0.1920.1054" timestamp="1602607646287" />
<state width="1878" height="374" key="GridCell.Tab.1.center" timestamp="1602607646287">
<state width="1878" height="374" key="GridCell.Tab.1.bottom/0.0.1920.1054@0.0.1920.1054" timestamp="1602610374479" />
<state width="1878" height="374" key="GridCell.Tab.1.center" timestamp="1602610374479">
<screen x="0" y="0" width="1920" height="1054" />
</state>
<state width="1878" height="374" key="GridCell.Tab.1.center/0.0.1920.1054@0.0.1920.1054" timestamp="1602607646287" />
<state width="1878" height="374" key="GridCell.Tab.1.left" timestamp="1602607646287">
<state width="1878" height="374" key="GridCell.Tab.1.center/0.0.1920.1054@0.0.1920.1054" timestamp="1602610374479" />
<state width="1878" height="374" key="GridCell.Tab.1.left" timestamp="1602610374478">
<screen x="0" y="0" width="1920" height="1054" />
</state>
<state width="1878" height="374" key="GridCell.Tab.1.left/0.0.1920.1054@0.0.1920.1054" timestamp="1602607646287" />
<state width="1878" height="374" key="GridCell.Tab.1.right" timestamp="1602607646287">
<state width="1878" height="374" key="GridCell.Tab.1.left/0.0.1920.1054@0.0.1920.1054" timestamp="1602610374478" />
<state width="1878" height="374" key="GridCell.Tab.1.right" timestamp="1602610374479">
<screen x="0" y="0" width="1920" height="1054" />
</state>
<state width="1878" height="374" key="GridCell.Tab.1.right/0.0.1920.1054@0.0.1920.1054" timestamp="1602607646287" />
<state width="1878" height="374" key="GridCell.Tab.2.bottom" timestamp="1602607646288">
<state width="1878" height="374" key="GridCell.Tab.1.right/0.0.1920.1054@0.0.1920.1054" timestamp="1602610374479" />
<state width="1878" height="374" key="GridCell.Tab.2.bottom" timestamp="1602610374481">
<screen x="0" y="0" width="1920" height="1054" />
</state>
<state width="1878" height="374" key="GridCell.Tab.2.bottom/0.0.1920.1054@0.0.1920.1054" timestamp="1602607646288" />
<state width="1878" height="374" key="GridCell.Tab.2.center" timestamp="1602607646287">
<state width="1878" height="374" key="GridCell.Tab.2.bottom/0.0.1920.1054@0.0.1920.1054" timestamp="1602610374481" />
<state width="1878" height="374" key="GridCell.Tab.2.center" timestamp="1602610374480">
<screen x="0" y="0" width="1920" height="1054" />
</state>
<state width="1878" height="374" key="GridCell.Tab.2.center/0.0.1920.1054@0.0.1920.1054" timestamp="1602607646287" />
<state width="1878" height="374" key="GridCell.Tab.2.left" timestamp="1602607646287">
<state width="1878" height="374" key="GridCell.Tab.2.center/0.0.1920.1054@0.0.1920.1054" timestamp="1602610374480" />
<state width="1878" height="374" key="GridCell.Tab.2.left" timestamp="1602610374480">
<screen x="0" y="0" width="1920" height="1054" />
</state>
<state width="1878" height="374" key="GridCell.Tab.2.left/0.0.1920.1054@0.0.1920.1054" timestamp="1602607646287" />
<state width="1878" height="374" key="GridCell.Tab.2.right" timestamp="1602607646287">
<state width="1878" height="374" key="GridCell.Tab.2.left/0.0.1920.1054@0.0.1920.1054" timestamp="1602610374480" />
<state width="1878" height="374" key="GridCell.Tab.2.right" timestamp="1602610374480">
<screen x="0" y="0" width="1920" height="1054" />
</state>
<state width="1878" height="374" key="GridCell.Tab.2.right/0.0.1920.1054@0.0.1920.1054" timestamp="1602607646287" />
<state width="1878" height="374" key="GridCell.Tab.3.bottom" timestamp="1602607646288">
<state width="1878" height="374" key="GridCell.Tab.2.right/0.0.1920.1054@0.0.1920.1054" timestamp="1602610374480" />
<state width="1878" height="374" key="GridCell.Tab.3.bottom" timestamp="1602610374482">
<screen x="0" y="0" width="1920" height="1054" />
</state>
<state width="1878" height="374" key="GridCell.Tab.3.bottom/0.0.1920.1054@0.0.1920.1054" timestamp="1602607646288" />
<state width="1878" height="374" key="GridCell.Tab.3.center" timestamp="1602607646288">
<state width="1878" height="374" key="GridCell.Tab.3.bottom/0.0.1920.1054@0.0.1920.1054" timestamp="1602610374482" />
<state width="1878" height="374" key="GridCell.Tab.3.center" timestamp="1602610374482">
<screen x="0" y="0" width="1920" height="1054" />
</state>
<state width="1878" height="374" key="GridCell.Tab.3.center/0.0.1920.1054@0.0.1920.1054" timestamp="1602607646288" />
<state width="1878" height="374" key="GridCell.Tab.3.left" timestamp="1602607646288">
<state width="1878" height="374" key="GridCell.Tab.3.center/0.0.1920.1054@0.0.1920.1054" timestamp="1602610374482" />
<state width="1878" height="374" key="GridCell.Tab.3.left" timestamp="1602610374481">
<screen x="0" y="0" width="1920" height="1054" />
</state>
<state width="1878" height="374" key="GridCell.Tab.3.left/0.0.1920.1054@0.0.1920.1054" timestamp="1602607646288" />
<state width="1878" height="374" key="GridCell.Tab.3.right" timestamp="1602607646288">
<state width="1878" height="374" key="GridCell.Tab.3.left/0.0.1920.1054@0.0.1920.1054" timestamp="1602610374481" />
<state width="1878" height="374" key="GridCell.Tab.3.right" timestamp="1602610374482">
<screen x="0" y="0" width="1920" height="1054" />
</state>
<state width="1878" height="374" key="GridCell.Tab.3.right/0.0.1920.1054@0.0.1920.1054" timestamp="1602607646288" />
<state width="1878" height="374" key="GridCell.Tab.4.bottom" timestamp="1602607646289">
<state width="1878" height="374" key="GridCell.Tab.3.right/0.0.1920.1054@0.0.1920.1054" timestamp="1602610374482" />
<state width="1878" height="374" key="GridCell.Tab.4.bottom" timestamp="1602610374484">
<screen x="0" y="0" width="1920" height="1054" />
</state>
<state width="1878" height="374" key="GridCell.Tab.4.bottom/0.0.1920.1054@0.0.1920.1054" timestamp="1602607646289" />
<state width="1878" height="374" key="GridCell.Tab.4.center" timestamp="1602607646288">
<state width="1878" height="374" key="GridCell.Tab.4.bottom/0.0.1920.1054@0.0.1920.1054" timestamp="1602610374484" />
<state width="1878" height="374" key="GridCell.Tab.4.center" timestamp="1602610374483">
<screen x="0" y="0" width="1920" height="1054" />
</state>
<state width="1878" height="374" key="GridCell.Tab.4.center/0.0.1920.1054@0.0.1920.1054" timestamp="1602607646288" />
<state width="1878" height="374" key="GridCell.Tab.4.left" timestamp="1602607646288">
<state width="1878" height="374" key="GridCell.Tab.4.center/0.0.1920.1054@0.0.1920.1054" timestamp="1602610374483" />
<state width="1878" height="374" key="GridCell.Tab.4.left" timestamp="1602610374483">
<screen x="0" y="0" width="1920" height="1054" />
</state>
<state width="1878" height="374" key="GridCell.Tab.4.left/0.0.1920.1054@0.0.1920.1054" timestamp="1602607646288" />
<state width="1878" height="374" key="GridCell.Tab.4.right" timestamp="1602607646289">
<state width="1878" height="374" key="GridCell.Tab.4.left/0.0.1920.1054@0.0.1920.1054" timestamp="1602610374483" />
<state width="1878" height="374" key="GridCell.Tab.4.right" timestamp="1602610374484">
<screen x="0" y="0" width="1920" height="1054" />
</state>
<state width="1878" height="374" key="GridCell.Tab.4.right/0.0.1920.1054@0.0.1920.1054" timestamp="1602607646289" />
<state width="1878" height="374" key="GridCell.Tab.4.right/0.0.1920.1054@0.0.1920.1054" timestamp="1602610374484" />
<state x="738" y="92" key="SettingsEditor" timestamp="1602603890848">
<screen x="0" y="0" width="1920" height="1054" />
</state>
@ -240,6 +241,10 @@
<screen x="0" y="0" width="1920" height="1054" />
</state>
<state x="100" y="99" width="1720" height="855" key="com.intellij.history.integration.ui.views.DirectoryHistoryDialog/0.0.1920.1054@0.0.1920.1054" timestamp="1602597391150" />
<state x="482" y="127" width="945" height="793" key="com.intellij.openapi.editor.actions.MultiplePasteAction$ClipboardContentChooser" timestamp="1602607983699">
<screen x="0" y="0" width="1920" height="1054" />
</state>
<state x="482" y="127" width="945" height="793" key="com.intellij.openapi.editor.actions.MultiplePasteAction$ClipboardContentChooser/0.0.1920.1054@0.0.1920.1054" timestamp="1602607983699" />
<state x="623" y="270" width="673" height="512" key="find.popup" timestamp="1602599001638">
<screen x="0" y="0" width="1920" height="1054" />
</state>
@ -248,9 +253,9 @@
<screen x="0" y="0" width="1920" height="1054" />
</state>
<state x="440" y="285" width="1009" height="647" key="org.intellij.images.editor.actions.BackgroundImageDialog/0.0.1920.1054@0.0.1920.1054" timestamp="1602598923528" />
<state x="623" y="231" width="672" height="678" key="search.everywhere.popup" timestamp="1602599024210">
<state x="623" y="231" width="672" height="678" key="search.everywhere.popup" timestamp="1602608996147">
<screen x="0" y="0" width="1920" height="1054" />
</state>
<state x="623" y="231" width="672" height="678" key="search.everywhere.popup/0.0.1920.1054@0.0.1920.1054" timestamp="1602599024210" />
<state x="623" y="231" width="672" height="678" key="search.everywhere.popup/0.0.1920.1054@0.0.1920.1054" timestamp="1602608996147" />
</component>
</project>

View File

@ -5,34 +5,6 @@ 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<string, BarcodeFragment> _fragments = new Dictionary<string, BarcodeFragment> {
["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<int, string> _formatTable = new Dictionary<int, string> {
[0] = "Null Customer Barcode",
[11] = "Standard Customer Barcode",
@ -44,6 +16,20 @@ namespace AusPostCode {
[77] = "International Business Reply Paid",
};
private readonly Dictionary<string, BarcodeFragment> _fragments = new Dictionary<string, BarcodeFragment> {
["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, 30, 22),
["ErrorCorrection"] = new BarcodeFragment(EncodingFormat.BarToDecimal, 12, -16),
};
// TODO: make these consts
private readonly char[] _lower = "abcdefghijklmnopqrstuvwxyz".ToCharArray();
private readonly char[] _upper = "ABCDEFGHIJKLMNOPQRSTUVWXYZ".ToCharArray();
private readonly int _formatCode;
public Barcode(string code) {
Code = code;
// - check for start and end bars
@ -68,20 +54,23 @@ namespace AusPostCode {
// 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");
Console.WriteLine("This code has a Customer Information 2 field, but I don't know how to process that yet 0uo");
}
else if (_formatCode == 62) {
Console.WriteLine($"Customer info: [{GetFragment(_fragments["CustomerInformation3"])}]");
}
// 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})";
public string Format =>
_formatTable.ContainsKey(_formatCode) ? _formatTable[_formatCode] : $"Unknown ({_formatCode})";
private List<string> Warnings { get; } = new List<string>();
private string Code { get; }
@ -113,14 +102,79 @@ namespace AusPostCode {
}
else if (chunk.Contains("3")) {
// not a ternary number
throw new ArgumentException($"{chunk} is not a valid identifier for format {data.ToString()}.");
throw new ArgumentException($"{chunk} is not a valid identifier for format {data}.");
}
else {
sb.Append(BaseConversion.FromBase(chunk, 3));
}
break;
case EncodingFormat.C:
// table C is a bit of a doozy.
// firstly, check for 222, which is zero.
if (chunk == "222") {
sb.Append("0");
}
// if it's any other valid ternary number (only digits 0,1,2), it's a capital letter.
else if (!chunk.Contains("3")) {
sb.Append(_upper[BaseConversion.FromBase(chunk, 3)]);
}
// if it starts with a 3, but is otherwise a valid ternary number, it's a digit from 1 through 9.
// the sequence is 300, 301, 302, 310, 311...
// because it starts at 0 (after stripping the leading 3), we add 1.
else if (chunk.StartsWith("3") && !chunk.Substring(1).Contains("3")) {
sb.Append(BaseConversion.FromBase(chunk.Substring(1), 3) + 1);
}
// 003 is space.
else if (chunk == "003") {
sb.Append(" ");
}
// 013 is hash.
else if (chunk == "013") {
sb.Append("#");
}
// and finally, anything that doesn't fall into any of the above is a lowercase letter.
else {
// this is by far the most difficult part when it comes to parsing the barcode formats (so far).
// i'm really bad at maths, and it took me a while to get this function into something (semi) readable
// down from the multi-cell spreadsheet formula mess that it started out as.
// i think this is the first time in my life i've ever done maths in a base other than 10, unless you
// count converting time differences.
// starting from 023, the lowercase numbers are expressed by a cyclical pattern.
// each "cycle" lasts for seven letters. let's use the letters "bcdefgh" as an example.
// b is 030 (12 in decimal). c follows at 031, d at 032, and e at 033. this is the first half of the cycle.
// during the second half of the cycle, letters are spaced *four* numbers apart,
// instead of one number apart like before.
// this means that e (033q == 15d) is *not* followed by f at 100q (16d), but at 103q (19d).
// g is found at 113q (23d), and then h is found at 123q (27d).
// i follows at 130q (28d), and the cycle repeats.
// the cycle resets at multiples of 16, which is very useful.
// the letters start at 11d, so we subtract 11 to start from zero instead.
var x = BaseConversion.FromBase(chunk, 4) - 11;
// you can tell how many times the cycle has looped by counting how many times 16 divides into the chunk.
// because the cycle is seven steps long, we should add 7 for every time 16 goes into the chunk.
// additionally, if we add 10 to x (and therefore making it effectively (chunk - 1)),
// we can mod it with 16 to get the repeating sequence '10, 11, 12, 13, 14, 2, 6'.
// we can use max() to replace the 2 with a 5, giving us '10, 11, 12, 13, 14, 5, 6'.
// if we only keep the units digit (by doing modulo 10), we get '0, 1, 2, 3, 4, 5, 6'.
// now we have a way to tell how many times we've completed the cycle (how many 16s go into the chunk),
// and a way to tell what step we're on in the cycle, we can combine these two pieces of knowledge to
// turn the messy letter codes into a set of integers from 0 to 25, increasing linearly by 1.
// in other words, when we're given a chunk like "032", we can tell that it refers to the 4th letter of
// the alphabet, and return that.
// i know the way this function is written is slightly unwieldy, but at the very least, it's better than
// wolfram alpha's suggested alternative form:
// -10 floor(1/10 max(5, x - 16 floor((x + 10)/16) + 10)) + max(5, -16 floor((x + 10)/16) + x + 10) + 7 floor(x/16)
// i guess wolfram really doesn't like the mod function? 0uo
sb.Append(_lower[x / 16 * 7 + Math.Max((x + 10) % 16, 5) % 10]);
}
break;
case EncodingFormat.BarToDecimal:
@ -167,6 +221,7 @@ namespace AusPostCode {
else {
sb.Append(BaseConversion.FromBase(chunk, 3));
}
break;
case EncodingFormat.C:
@ -182,5 +237,23 @@ namespace AusPostCode {
return sb.ToString();
}
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 enum EncodingFormat {
N,
C,
BarToDecimal,
}
}
}

View File

@ -29,9 +29,12 @@ namespace AusPostCode {
// - a filler bar of 3
// - a reed-solomon code of 001101103221 == 10826817
// - end bars
const string testCode = "1301012112011202120020300110110322113";
// const string testCode = "1301012112011202120020300110110322113";
var barcode = new Barcode(testCode);
// const string testCodeCustomer3 = "1320021211120120021201000001002300301302333333333333313133002021313";
const string testCodeCustomer3 = "1320021211120120021201003021130003120033203230023003313133002021313";
var barcode = new Barcode(testCodeCustomer3);
}
public class Options {