Skip to content

Commit

Permalink
Merge pull request #2 from Defkon1/developizza
Browse files Browse the repository at this point in the history
Added experimental support for ANPR Identifier (ID ANPR)
  • Loading branch information
Defkon1 authored May 26, 2023
2 parents edfcc42 + 2668e5e commit a569d23
Show file tree
Hide file tree
Showing 5 changed files with 212 additions and 1 deletion.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

## [1.1.0] - 2023-05-26

- Added experimental support for ANPR Identifier (ID ANPR) - [see issue #1 for more details](https://github.com/Defkon1/italian-toolkit/issues/1)

## [1.0.1] - 2023-05-15

### Changed
Expand Down Expand Up @@ -34,6 +38,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0


[Unreleased]: https://github.com/defkon1/italian-toolkit/compare/v1.0.1...HEAD
[1.1.0]: https://github.com/defkon1/italian-toolkit/compare/v1.0.1...v1.1.0
[1.0.1]: https://github.com/defkon1/italian-toolkit/compare/v0.1.8-beta...v1.0.1
[0.1.8-beta]: https://github.com/defkon1/italian-toolkit/compare/v0.1.7-beta...v0.1.8-beta
[0.1.7-beta]: https://github.com/defkon1/italian-toolkit/releases/tag/v0.1.7-beta
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ The library is available on [NuGet](https://www.nuget.org/packages/ItalianToolki
- [x] Homocodies calculation from base fiscal code
- [x] Homocody validation
- [ ] Birthplace validation
- [x] ANPR Identifier (ID ANPR)
- [x] Formal validation (EXPERIMENTAL)
- [x] Check character calculation (EXPERIMENTAL)
- [ ] Transports
- [ ] Car plates validation (pre 1994)
- [ ] Car plates validation (post 1994)
Expand All @@ -31,6 +34,10 @@ The library is available on [NuGet](https://www.nuget.org/packages/ItalianToolki
- [ ] Municipalities and metropolitan areas
- [ ] Other? - File an issue!

## ANPR Identifier support (EXPERIMENTAL)

The validation methods for ANPR Identifier are released as EXPERIMENTAL because the *official technical specification is not yet available*, and it was written reverse engineering the available technical information (and a bit of try & guess).

# Contributing

Contributions are welcome. Feel free to file issues and pull requests on the repo and we'll address them as we can.
Expand Down
75 changes: 75 additions & 0 deletions src/ItalianToolkit.Tests/People/AnprCodeHelperTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
using ItalianToolkit.People;
using NUnit.Framework;
using System;

namespace ItalianToolkit.Tests.People
{
internal class AnprCodeHelperTests
{
[SetUp]
public void Setup()
{
}

[Test]
public void AnprCodeShouldBeValidated()
{
var validCodes = new []
{
"FL46721JJ",
"fl46721jj"
};

foreach(var code in validCodes)
{
var result = AnprCodeHelper.IsFormallyValid(code);

Assert.IsTrue(result);
}
}

[Test]
public void AnprCodeShouldFailValidation()
{
var notValidCodes = new[]
{
"WrongCode",
"Shorter",
"LongerThanExpected",
"3edr7y89",
"POL098231"
};

foreach (var code in notValidCodes)
{
var result = AnprCodeHelper.IsFormallyValid(code);

Assert.IsFalse(result);
}
}

[Test]
public void AnprCodeValidationShouldThrow()
{
Assert.Throws<ArgumentException>(() =>
{
var result = AnprCodeHelper.IsFormallyValid(string.Empty);
});

Assert.Throws<ArgumentException>(() =>
{
var result = AnprCodeHelper.IsFormallyValid(null);
});

Assert.Throws<ArgumentException>(() =>
{
var result = AnprCodeHelper.IsFormallyValid(" ");
});

Assert.Throws<ArgumentException>(() =>
{
var result = AnprCodeHelper.IsFormallyValid("POL?98231");
});
}
}
}
123 changes: 123 additions & 0 deletions src/ItalianToolkit/People/AnprCodeHelper.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
using System;
using System.Collections.Generic;
using System.Linq;

namespace ItalianToolkit.People
{
public static class AnprCodeHelper
{
private static readonly Dictionary<char, int> _charsMap = new Dictionary<char, int>()
{
{'0', 0},
{'1', 1},
{'2', 2},
{'3', 3},
{'4', 4},
{'5', 5},
{'6', 6},
{'7', 7},
{'8', 8},
{'9', 9},
{'A', 10},
{'B', 11},
{'C', 12},
{'D', 13},
{'E', 14},
{'F', 15},
{'G', 16},
{'H', 17},
{'I', 18},
{'J', 19},
{'K', 20},
{'L', 21},
{'M', 22},
{'N', 23},
{'O', 24},
{'P', 25},
{'Q', 26},
{'R', 27},
{'S', 28},
{'T', 29},
{'U', 30},
{'V', 31},
{'W', 32},
{'X', 33},
{'Y', 34},
{'Z', 35}
};

private static readonly int _OddMultiplier = 2;

private static readonly int _EvenMultiplier = 1;

/// <summary>
/// Determines whether the specified value is a formally valid ANPR identifier.
/// </summary>
/// <param name="anprCode">The identifier to validate</param>
/// <returns><code>true</code> if the specified value is valid, <code>false</code> otherwise</returns>
/// <exception cref="ArgumentException">The specified value is <code>null</code> or empty.</exception>
public static bool IsFormallyValid(string anprCode)
{
if (string.IsNullOrWhiteSpace(anprCode))
{
throw new ArgumentException(nameof(anprCode));
}

anprCode = anprCode.Trim().ToUpper();

if (anprCode.Length != 9)
{
return false;
}

var checkDigit = CalculateCheckCharacter(anprCode);

return checkDigit == anprCode[anprCode.Length - 1];
}

/// <summary>
/// Calculates the check character of the given ANPR identifier
/// </summary>
/// <param name="anprCode">The identifier for which to calculate the check character</param>
/// <returns>a <code>char</code> representing the check character</returns>
/// <exception cref="ArgumentException">The specified value is <code>null</code>, empty, longer than 9 characters, shorter than 8 characters or containing unvalid characters</exception>
public static char CalculateCheckCharacter(string anprCode)
{
if (string.IsNullOrWhiteSpace(anprCode) || anprCode.Length > 9 || anprCode.Length < 8)
{
throw new ArgumentException(nameof(anprCode));
}

foreach (char c in anprCode)
{
if (!_charsMap.ContainsKey(c))
{
throw new ArgumentException(nameof(anprCode));
}
}

var testCode = anprCode.Length == 9 ? anprCode.Substring(0, 8) : anprCode;

int sum = 0;
int n = _charsMap.Count;

for (int i = testCode.Length - 1; i >= 0; i--)
{
int factor = i % 2 == 0 ? _EvenMultiplier : _OddMultiplier;

int codePoint = _charsMap[testCode[i]];
int addend = factor * codePoint;

// sum the digits of the "addend" as expressed in base "n"
addend = Convert.ToInt32(addend / n) + (addend % n);
sum += addend;
}

// calculate the number that must be added to the "sum" to make it divisible by N
int remainder = sum % n;
int checkCodePoint = (n - remainder) % n;

return _charsMap.First(c => c.Value == checkCodePoint).Key;
}
}
}
3 changes: 2 additions & 1 deletion src/ItalianToolkit/version.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
{
"$schema": "https://raw.githubusercontent.com/dotnet/Nerdbank.GitVersioning/master/src/NerdBank.GitVersioning/version.schema.json",
"version": "1.0",
"version": "1.1",
"versionHeightOffset": -1,
"publicReleaseRefSpec": [
"^refs/heads/maindolino$" // we release out of master
],
Expand Down

0 comments on commit a569d23

Please sign in to comment.