Oggi un collega mi ha chiesto se con una regular expression era possibile unificare dei numeri decimali serializzati con un formato eterogeneo, per potere essere convertiti con l'InvariantCulture di .net che prevede il punto come separatore dei decimali e la virgola per le migliaia.
La prima cosa che mi è saltata in mente (forse troppo elaborata, boh non so, ma funziona...) era quella semplicemente di togliere tutti i separatori delle migliaia prima del separatore dei decimali, tenendo conto che potrebbero arrivare numeri con la virgola o con il punto come separatore dei decimali.
Questa è la lista dei casi (input):
1.000.000,00
1.000.000
1,000.30
1000.0
1,000,000.30
1,000,000,000.30
1,000,000
1000,0
e il risultato che vogliamo è questo (output fase 1):
1000000,00
1000000
1000.30
1000.0
1000000.30
1000000000.30
1000000
1000,0
in mondo poi da eseguire un altro replace(",", ".") per concludere la "pulizia" e arrivare in fine a questo (output fase 2):
1000000.00
1000000
1000.30
1000.0
1000000.30
1000000000.30
1000000
1000.0
Per riuscire ad ottenere la scrematura della prima fase ho utilizzato le funzionalità di lookaround delle regular:
(?:,(?=(?:\d+,?)+\.))|(?:\.(?=(?:\d+\.?)+,))|(?:(?<=\.\d+)\.)|(?:\.(?=\d+\.))|(?:(?<=,\d+),)|(?:,(?=\d+,))
ogni pipe "|" (or) praticamente serve ad individuare i separatori delle migliaia nei vari formati con le varie regole del caso:
(?:,(?=(?:\d+,?)+\.))
(?:\.(?=(?:\d+\.?)+,))
(?:(?<=\.\d+)\.)
(?:\.(?=\d+\.))
(?:(?<=,\d+),)
(?:,(?=\d+,))
Dopo la prima "scrematura" basta eseguire un semplce replace della virgola con il punto e fare il cast con l'InvariantCulture per avere la certezza di avere un vero decimale qualsiasi (o quasi) formato possa arrivare, che seguono quelle regole:
void Main()
{
string SubjectString = "1000,1";
string ResultString = null;
Regex RegexObj = new Regex(@"(?:,(?=(?:\d+,?)+\.))|(?:\.(?=(?:\d+\.?)+,))|(?:(?<=\.\d+)\.)|(?:\.(?=\d+\.))|(?:(?<=,\d+),)|(?:,(?=\d+,))");
ResultString = RegexObj.Replace(SubjectString, "").Replace(",", ".");
decimal result = decimal.Parse(ResultString, System.Globalization.NumberStyles.Any, System.Globalization.CultureInfo.InvariantCulture);
Console.WriteLine(result);
}
Piccolo appunto: la regular in questione
non sa distinguere il caso in cui abbiamo un numero così:
100.001
oppure
100,001
non può sapere se volevamo dire cento
virgola uno oppure centomila e uno, ma nel caso del mio collega non era
necessario prendere altre posizioni, nel caso in cui si dovesse applicare in
una situazione più generalizzata si potrebbero individuare questi casi
particolari (sempre con una regular) ed evitare di prendere in considerazione
il numero, magari mandando un email per avvisare del problema.