How to validate a (country specific) phone number - c#

A valid phone number contains:
Less than 9 characters
A "+" at the start
Only digits.
I'm trying to use regular expressions but I've only started using them and I'm not good at it. The code I have so far is:
static void Main(string[] args)
{
Console.WriteLine("Enter a phone number.");
string telNo = Console.ReadLine();
if (Regex.Match(telNo, #"^(\+[0-9])$").Success)
Console.WriteLine("correctly entered");
else
Console.WriteLine("incorrectly entered");
Console.ReadLine();
}
But I don't know how to check the length of the string this way. Any help is appreciated.

Jacek's regex works fine
public class Program
{
public static void Main()
{
Console.WriteLine("Enter a phone number.");
string telNo = Console.ReadLine();
Console.WriteLine("{0}correctly entered", IsPhoneNumber(telNo) ? "" : "in");
Console.ReadLine();
}
public static bool IsPhoneNumber(string number)
{
return Regex.Match(number, #"^(\+[0-9]{9})$").Success;
}
}

Your regex should look like this, you need information about char counter
#"^(\+[0-9]{9})$"

DON'T USE A REGULAR EXPRESSION!!
There are too many variables for a regex to be of any use. Instead, just remove all characters from your string that are not 0-9, and then check to see if you have the correct number of digits left. Then it doesn't matter what extra stuff the user includes or doesn't include... ()x-+[] etc etc, as it just strips them all out and only counts the characters 0-9.
I've got a string extension that works great, and allows for a wide range of formats. It accepts an IsRequired parameter. So, you can validate a phone number like this:
string phone = "(999)999-9999"
bool isValidPhone = phone.ValidatePhoneNumber(true) // returns true
string phone ="1234567890"
bool isValidPhone = phone.ValidatePhoneNumber(true) // returns true
string phone = ""
bool isValidPhone = phone.ValidatePhoneNumber(false) // not required, so returns true
string phone = ""
bool isValidPhone = phone.ValidatePhoneNumber(true) // required, so returns false
string phone ="12345"
bool isValidPhone = phone.ValidatePhoneNumber(true) // returns false
string phone ="foobar"
bool isValidPhone = phone.ValidatePhoneNumber(true) // returns false
Here's the code (assumes a 10-digit American phone number. Adjust accordingly):
public static class StringExtensions
{
/// <summary>
/// Checks to be sure a phone number contains 10 digits as per American phone numbers.
/// If 'IsRequired' is true, then an empty string will return False.
/// If 'IsRequired' is false, then an empty string will return True.
/// </summary>
/// <param name="phone"></param>
/// <param name="IsRequired"></param>
/// <returns></returns>
public static bool ValidatePhoneNumber(this string phone, bool IsRequired)
{
if (string.IsNullOrEmpty(phone) & !IsRequired)
return true;
if (string.IsNullOrEmpty(phone) & IsRequired)
return false;
var cleaned = phone.RemoveNonNumeric();
if (IsRequired)
{
if (cleaned.Length == 10)
return true;
else
return false;
}
else
{
if (cleaned.Length == 0)
return true;
else if (cleaned.Length > 0 & cleaned.Length < 10)
return false;
else if (cleaned.Length == 10)
return true;
else
return false; // should never get here
}
}
/// <summary>
/// Removes all non numeric characters from a string
/// </summary>
/// <param name="phone"></param>
/// <returns></returns>
public static string RemoveNonNumeric(this string phone)
{
return Regex.Replace(phone, #"[^0-9]+", "");
}
}

Expanding upon one of the answers provided above, the method I came up with to also handle a few phone number delivery styles as well as international phone number is
internal static bool IsValidPhoneNumber(this string This)
{
var phoneNumber = This.Trim()
.Replace(" ", "")
.Replace("-", "")
.Replace("(", "")
.Replace(")", "");
return Regex.Match(phoneNumber, #"^\+\d{5,15}$").Success;
}

Something like this could work:
^+\d{0,9}
But I would suggest playing around with a regex tester to learn more about how regular expressions work. I still like to use them heavily myself, as I don't write regular expressions often. Here is one example but there are many more out there.
https://regex101.com/

Simple function for Valid USAPhoneNumber or not.
/// <summary>
/// Allows phone number of the format: NPA = [2-9][0-8][0-9] Nxx = [2-9] [0-9][0-9] Station = [0-9][0-9][0-9][0-9]
/// </summary>
/// <param name="strPhone"></param>
/// <returns></returns>
public static bool IsValidUSPhoneNumber(string strPhone)
{
string regExPattern = #"^[01]?[- .]?(\([2-9]\d{2}\)|[2-9]\d{2})[- .]?\d{3}[- .]?\d{4}$";
return MatchStringFromRegex(strPhone, regExPattern);
}
// Function which is used in IsValidUSPhoneNumber function
public static bool MatchStringFromRegex(string str, string regexstr)
{
str = str.Trim();
System.Text.RegularExpressions.Regex pattern = new System.Text.RegularExpressions.Regex(regexstr);
return pattern.IsMatch(str);
}

If you're looking for a country specific regex, try this expression which works for all Australian (+61-) numbers. I've put comments on how to go about varying it for other uses.
public static bool IsValidPhoneNumber(string phoneNumber)
{
//will match +61 or +61- or 0 or nothing followed by a nine digit number
return Regex.Match(phoneNumber,
#"^([\+]?61[-]?|[0])?[1-9][0-9]{8}$").Success;
//to vary this, replace 61 with an international code of your choice
//or remove [\+]?61[-]? if international code isn't needed
//{8} is the number of digits in the actual phone number less one
}

This solution validates every test criteria for validating a phone number, it also leverages from the Regex API. Criteria includes spacing, any non numeric values, area codes (which you specify), number of values (digits) the phone number should have, and also includes error messaging as well as phone number old and new state.
Here is the source code:
public class PhoneNumberValidator
{
public string ErrorMessage { get; set; }
public int PhoneNumberDigits { get; set; }
public string CachedPhoneNumber { get; set; }
private Dictionary<int, string> VaildAreaCodes()
{
return new Dictionary<int, string>
{
[3] = "0",
[4] = "27"
};
}
private bool IsInteger(string value)
{
return int.TryParse(value, out int result);
}
private string GetConsecutiveCharsInPhoneNumberStr(string phoneNumber)
{
switch (PhoneNumberDigits)
{
case 0:
case 10:
PhoneNumberDigits = 10;
return phoneNumber.Substring(phoneNumber.Length - 7);
case 11:
return phoneNumber.Substring(phoneNumber.Length - 8);
default:
return string.Empty;
}
}
private bool IsValidAreaCode(ref string phoneNumber, string areaCode)
{
if (!IsInteger(areaCode))
{
ErrorMessage = "Area code characters of Phone Number value should only contain integers.";
return false;
}
var areaCodeLength = areaCode.Length;
var invalidAreaCodeMessage = "Phone Number value contains invalid area code.";
switch (areaCodeLength)
{
case 2:
phoneNumber = string.Concat("0", phoneNumber);
return true;
case 3:
if (!areaCode.StartsWith(VaildAreaCodes[3]))
ErrorMessage = invalidAreaCodeMessage;
return string.IsNullOrWhiteSpace(ErrorMessage) ? true : false;
case 4:
if (areaCode.StartsWith(VaildAreaCodes[4]))
{
phoneNumber = string.Concat("0", phoneNumber.Remove(0, 2)); // replace first two charaters with zero
return true;
}
ErrorMessage = invalidAreaCodeMessage;
return false;
default:
ErrorMessage = invalidAreaCodeMessage;
return false;
}
}
public bool IsValidPhoneNumber(ref string phoneNumber)
{
CachedPhoneNumber = phoneNumber;
if (string.IsNullOrWhiteSpace(phoneNumber))
{
ErrorMessage = "Phone Number value should not be equivalent to null.";
return false;
}
phoneNumber = Regex.Replace(phoneNumber, " {2,}", string.Empty); // remove all whitespaces
phoneNumber = Regex.Replace(phoneNumber, "[^0-9]", string.Empty); // remove all non numeric characters
var lastConsecutiveCharsInPhoneNumberStr = GetConsecutiveCharsInPhoneNumberStr(phoneNumber);
if (string.IsNullOrWhiteSpace(lastConsecutiveCharsInPhoneNumberStr))
{
ErrorMessage = "Phone Number value not supported.";
return false;
}
if (!IsInteger(lastConsecutiveCharsInPhoneNumberStr))
{
ErrorMessage = "Last consecutive characters of Phone Number value should only contain integers.";
return false;
}
var phoneNumberAreaCode = phoneNumber.Replace(lastConsecutiveCharsInPhoneNumberStr, "");
if (!IsValidAreaCode(ref phoneNumber, phoneNumberAreaCode))
{
return false;
}
if (phoneNumber.Length != PhoneNumberDigits)
{
ErrorMessage = string.Format("Phone Number value should contain {0} characters instead of {1} characters.", PhoneNumberDigits, phoneNumber.Length);
return false;
}
return true;
}
}
The solution is highly configurable and may be used for any digits phone number as well as area code.

Related

How do I use regex to validate my string?

I'm trying to use a method to validate a string that has two capital letters at the start and 3 digits after that. The one I am writing at the minute is:
static void Main(string[] args)
{
Console.WriteLine("Enter stock quantity:");
string qty = Console.ReadLine();
string code = enterStockCode();
}
static string enterStockCode()
{
string[] pLetters = new string[] { "BL", "FS", "HJ", "PX" };
bool isValid = false;
while (isValid == false)
{
Console.WriteLine("Enter stockcode:");
string stockCode = Console.ReadLine();
char[] stockCodeSplit = stockCode.ToCharArray();
var vLetters = (stockCodeSplit[0] + stockCodeSplit[1]);
var vNumbers = (stockCodeSplit[2] + stockCodeSplit[3] + kCodeSplit[4]);
// I'd like to know if there is a better way of splitting this string
// by using regular expressions or another way.
foreach (var Letter in pLetters)
{
if (vNumbers.ToString() == Letter)
{
if (Regex.Match(vNumbers.ToString(), #"^\d[0-9]{2}$").Success)
return stockCode;
Console.WriteLine("Invalid stock code");
}
}
}
return "Invalid";
}
(Also I would like to know if the "2" in:
#"^\d[0-9]{2}$"
means 2 times or three times as c# uses index of 0.)
Any help at all is appreciated.
please try:
^[A-Z]{2}\d{3}
If you just want to find an expression starting with 2 capital letters followed by 3 digits you should not put an ending limiter, just the starting.
The 2 means 2 times.

Test Console.ReadLine(); input type

I'm making a console app in which I'll ask the user for a phone number int main_phone, but if he types a string thats not convertible to int such as "Caramel" I want to give him a warning and ask for a number.
Well I thought a recursive function would do, but I'm lacking logical aptitude for this one. What I came up with was the following code snippet:
static int? TryParse(string str)
{
int tmp;
if (int.TryParse(str, out tmp)) {
return tmp;
}
return null;
}//End of TryParse();
static int? verifyParse(string str, string lable)
{
int? testedVar = TryParse(str);
if (testedVar == null)
{
Console.WriteLine("Este campo pode apenas conter dígitos de 0 à 9.");
Console.Write(lable);
verifyParse(Console.ReadLine(), lable);
return null;
}
else
{
return testedVar;
}
}//End of verifyParse();
Console.Write("Phone(main):");
int? main_phone = verifyParse(Console.ReadLine(),"Telefone (Principal):");
The problem is that if the user inputs "string" main_phone will comeback as null, then it asks for a number between 0 and 9, but even if the user passes a convertible string , such as "12", main_phone will still be null.
How can I fix that?
EDIT 1:
What I really need is a function that if the string can be converted to an int it returns the int, else it gives me an error and asks me to input something between 0 and 9. I find this hard because the function would have to be able to return an int or run it self all over again(to require and test a valid input), but it can't because either it returns the int or it doesn't return nothing.
EDIT 2:
Here is a little update on my problem:
static int TryParse(string lable)
{
string str = Console.ReadLine();
Regex regex = new Regex(#"\d+");
while (!regex.IsMatch(str))
{
Console.WriteLine("Insira apenas dígitos de 0-9, por favor.");
Console.Write(lable);
str = Console.ReadLine();
}
return Convert.ToInt32(str);
}//End of TryParse();
Console.Write("Telefone (Principal):");
int main_phone = TryParse("Telefone (Principal):");
It works really well, except when I input something like "1a". In that case I'll get this error:
System.FormatException: Input string was not in a correct format.
at System.Number.StringToNumber(String str, NumberStyles options, NumberBuffer& number, NumberFormatInfo info, Boolean parseDecimal)
at System.Number.ParseInt32(String s, NumberStyles style, NumberFormatInfo info)
at System.Convert.ToInt32(String value)
at App1.Program.TryParse(String lable)
at App1.Program.Main(String[] args)
Here's a solution that loops until a good match is produced, using regex validation
Regex regex = new Regex(#"\d{3}-?\d{3}-?\d{4}");
string phonenumber = Console.ReadLine();
while (!regex.IsMatch(phonenumber))
{
Console.WriteLine("Bad Input");
phonenumber = Console.ReadLine();
}
return Convert.ToInt32(phonenumber.Replace("-",""));
which will match 5555555555 and 555-555-5555, and return the int value.
How about you just read in as a string what the user puts in, replace all "-" with "", then check if it numeric and check length. No need to make a recursive method for such a simple task.
how about this
[VB.NET]
Function CheckPhoneNumber(ByVal Number As String) As String
Number = Number.Replace("-","")
Number = Number.Replace("(","")
Number = Number.Replace(")","")
If Not IsNumeric(Number) Then
Return "Error"
Else
Return Number
End If
End Function
[C#]
public string CheckPhoneNumber(string Number)
{
Number = Number.Replace("-", "");
Number = Number.Replace("(", "");
Number = Number.Replace(")", "");
if (!Information.IsNumeric(Number)) {
return "Error";
} else {
return Number;
}
}
What about just using int.TryParse?
void Main()
{
var input = Console.ReadLine();
int phone;
while(!int.TryParse(input, out phone)){
Console.WriteLine("Please enter a number");
input = Console.ReadLine();
}
Console.WriteLine("Success");
}
To cleanup the input, you can replace the "-"
var input = Console.ReadLine().Replace("-", "");
Could strip out any extra characters ('+', '-', '#', '(', ')', and '*') then try
bool successful;
int result;
if(!int.tryparse(string, result))
{
Console.WriteLine("Invalid phone number");
}
else
{
Console.WriteLine("Valid phone number");
}
Or whatever you like.
Of course the alternative is to use REGEX. (note: I'm stealing this from Regex Hero found here: http://regexhero.net/tester/)
string strRegex = #"[+]?[\d][\d\s()-.]{8,}[\d]";
RegexOptions myRegexOptions = RegexOptions.None;
Regex myRegex = new Regex(strRegex, myRegexOptions);
foreach (Match myMatch in myRegex.Matches(strTargetString))
{
if (myMatch.Success)
{
// Add your code here
}
}
Try this modified version of your code segment:
static int? verifyParse(string str, string lable)
{
int? testedVar = TryParse(str);
if (testedVar == null)
{
Console.WriteLine("Este campo pode apenas conter dígitos de 0 à 9.");
Console.Write(lable);
int? newVal = verifyParse(Console.ReadLine(), lable);
if (newVal != null)
return newVal;
else
return null;
}
else
{
return testedVar;
}
}//End of verifyParse();

Regex camelcase in c#

I'm trying to use regex to convert a string like this "North Korea"
to a string like "northKorea" - does someone know how I might accomplish this in c# ?
Cheers
if you know all your input strings are in title case (like "North Korea") you can simply do:
string input = "North Korea";
input = input.Replace(" ",""); //remove spaces
string output = char.ToLower(input[0]) +
input.Substring(1); //make first char lowercase
// output = "northKorea"
if some of your input is not in title case you can use TextInfo.ToTitleCase
string input = "NoRtH kORea";
input = System.Globalization.CultureInfo.CurrentCulture.TextInfo.ToTitleCase(input);
input = input.Replace(" ",""); //remove spaces
string output = char.ToLower(input[0]) +
input.Substring(1); //make first char lowercase
// output = "northKorea"
Forget regex.
All you need is a camelCase conversion algorithm:
See here:
http://www.codekeep.net/snippets/096fea45-b426-40fd-8beb-dec49d8a8662.aspx
Use this one:
string camelCase = ConvertCaseString(a, Case.CamelCase);
Copy-pasted in case it goes offline:
void Main() {
string a = "background color-red.brown";
string camelCase = ConvertCaseString(a, Case.CamelCase);
string pascalCase = ConvertCaseString(a, Case.PascalCase);
}
/// <summary>
/// Converts the phrase to specified convention.
/// </summary>
/// <param name="phrase"></param>
/// <param name="cases">The cases.</param>
/// <returns>string</returns>
static string ConvertCaseString(string phrase, Case cases)
{
string[] splittedPhrase = phrase.Split(' ', '-', '.');
var sb = new StringBuilder();
if (cases == Case.CamelCase)
{
sb.Append(splittedPhrase[0].ToLower());
splittedPhrase[0] = string.Empty;
}
else if (cases == Case.PascalCase)
sb = new StringBuilder();
foreach (String s in splittedPhrase)
{
char[] splittedPhraseChars = s.ToCharArray();
if (splittedPhraseChars.Length > 0)
{
splittedPhraseChars[0] = ((new String(splittedPhraseChars[0], 1)).ToUpper().ToCharArray())[0];
}
sb.Append(new String(splittedPhraseChars));
}
return sb.ToString();
}
enum Case
{
PascalCase,
CamelCase
}
You could just split it and put it back together:
string[] split = ("North Korea").Split(' ');
StringBuilder sb = new StringBuilder();
for (int i = 0; i < split.Count(); i++)
{
if (i == 0)
sb.Append(split[i].ToLower());
else
sb.Append(split[i]);
}
Edit: Switched to a StringBuilder instead, like Bazzz suggested.
This builds on Paolo Falabella's answer as a String extension and handles a few boundary cases such as empty string. Since there is some confusion between CamelCase and camelCase, I called it LowerCamelCase as described on Wikipedia. I resisted the temptation to go with nerdCaps.
internal static string ToLowerCamelCase( this string input )
{
string output = "";
if( String.IsNullOrEmpty( input ) == false )
{
output = System.Globalization.CultureInfo.CurrentCulture.TextInfo.ToTitleCase( input ); //in case not Title Case
output = output.Replace( " ", "" ); //remove any white spaces between words
if( String.IsNullOrEmpty( output ) == false ) //handles the case where input is " "
{
output = char.ToLower( output[0] ) + output.Substring( 1 ); //lowercase first (even if 1 character string)
}
}
return output;
}
Use:
string test = "Foo Bar";
test = test.ToLowerCamelCase();
... //test is now "fooBar"
Update:
toong raised a good point in the comments - this will not work for graphemes. See the link provided by toong. There are also examples of iterating graphemes here and here if you want to tweak the above code for graphemes.
String::Split definitely is one of my pet peeves. Also, none of the other answers deal with:
Cultures
All forms of word seperators
Numbers
What happens when it starts with word seperators
I tried to get it as close as possible to what you would find in base class library code:
static string ToCamelCaseInvariant(string value) { return ToCamelCase(value, true, CultureInfo.InvariantCulture); }
static string ToCamelCaseInvariant(string value, bool changeWordCaps) { return ToCamelCase(value, changeWordCaps, CultureInfo.InvariantCulture); }
static string ToCamelCase(string value) { return ToCamelCase(value, true, CultureInfo.CurrentCulture); }
static string ToCamelCase(string value, bool changeWordCaps) { return ToCamelCase(value, changeWordCaps, CultureInfo.CurrentCulture); }
/// <summary>
/// Converts the given string value into camelCase.
/// </summary>
/// <param name="value">The value.</param>
/// <param name="changeWordCaps">If set to <c>true</c> letters in a word (apart from the first) will be lowercased.</param>
/// <param name="culture">The culture to use to change the case of the characters.</param>
/// <returns>
/// The camel case value.
/// </returns>
static string ToCamelCase(string value, bool changeWordCaps, CultureInfo culture)
{
if (culture == null)
throw new ArgumentNullException("culture");
if (string.IsNullOrEmpty(value))
return value;
var result = new StringBuilder(value.Length);
var lastWasBreak = true;
for (var i = 0; i < value.Length; i++)
{
var c = value[i];
if (char.IsWhiteSpace(c) || char.IsPunctuation(c) || char.IsSeparator(c))
{
lastWasBreak = true;
}
else if (char.IsNumber(c))
{
result.Append(c);
lastWasBreak = true;
}
else
{
if (result.Length == 0)
{
result.Append(char.ToLower(c, culture));
}
else if (lastWasBreak)
{
result.Append(char.ToUpper(c, culture));
}
else if (changeWordCaps)
{
result.Append(char.ToLower(c, culture));
}
else
{
result.Append(c);
}
lastWasBreak = false;
}
}
return result.ToString();
}
// Tests
' This is a test. 12345hello world' = 'thisIsATest12345HelloWorld'
'--north korea' = 'northKorea'
'!nOrTH koreA' = 'northKorea'
'System.Console.' = 'systemConsole'
Try the following:
var input = "Hi my name is Rony";
var subStrs = input.ToLower().Split(' ');
var output = "";
foreach(var s in subStrs)
{
if(s!=subStrs[0])
output += s.First().ToString().ToUpper() + String.Join("", s.Skip(1));
else
output += s;
}
should get "hiMyNameIsRony" as the output
string toCamelCase(string s)
{
if (s.Length < 2) return s.ToLower();
return Char.ToLowerInvariant(s[0]) + s.Substring(1);
}
similar to Paolo Falabella's code but survives empty strings and 1 char strings.

How to ignore case in String.replace

string sentence = "We know it contains 'camel' word.";
// Camel can be in different cases:
string s1 = "CAMEL";
string s2 = "CaMEL";
string s3 = "CAMeL";
// ...
string s4 = "Camel";
// ...
string s5 = "camel";
How to replace 'camel' in sentence with 'horse' despite of string.Replace doesn't support ignoreCase on left string?
Use a regular expression:
var regex = new Regex( "camel", RegexOptions.IgnoreCase );
var newSentence = regex.Replace( sentence, "horse" );
Of course, this will also match words containing camel, but it's not clear if you want that or not.
If you need exact matches you can use a custom MatchEvaluator.
public static class Evaluators
{
public static string Wrap( Match m, string original, string format )
{
// doesn't match the entire string, otherwise it is a match
if (m.Length != original.Length)
{
// has a preceding letter or digit (i.e., not a real match).
if (m.Index != 0 && char.IsLetterOrDigit( original[m.Index - 1] ))
{
return m.Value;
}
// has a trailing letter or digit (i.e., not a real match).
if (m.Index + m.Length != original.Length && char.IsLetterOrDigit( original[m.Index + m.Length] ))
{
return m.Value;
}
}
// it is a match, apply the format
return string.Format( format, m.Value );
}
}
Used with the previous example to wrap the match in a span as:
var regex = new Regex( highlightedWord, RegexOptions.IgnoreCase );
foreach (var sentence in sentences)
{
var evaluator = new MatchEvaluator( match => Evaluators.Wrap( match, sentence, "<span class='red'>{0}</span>" ) );
Console.WriteLine( regex.Replace( sentence, evaluator ) );
}
Add an extension method for string to do the trick:
Usage:
string yourString = "TEXTTOREPLACE";
yourString.Replace("texttoreplace", "Look, I Got Replaced!", StringComparison.OrdinalIgnoreCase);
Code:
using System;
using System.Collections.Generic;
using System.IO;
public static class Extensions
{
public static string Replace(this string source, string oldString, string newString, StringComparison comp)
{
int index = source.IndexOf(oldString, comp);
// Determine if we found a match
bool MatchFound = index >= 0;
if (MatchFound)
{
// Remove the old text
source = source.Remove(index, oldString.Length);
// Add the replacemenet text
source = source.Insert(index, newString);
}
// recurse for multiple instances of the name
if (source.IndexOf(oldString, comp) != -1)
{
source = Replace(source, oldString, newString, comp);
}
return source;
}
}
Here's an extension method taking a StringComparison, using string.IndexOf:
[Pure]
public static string Replace(this string source, string oldValue, string newValue, StringComparison comparisonType)
{
if (source.Length == 0 || oldValue.Length == 0)
return source;
var result = new System.Text.StringBuilder();
int startingPos = 0;
int nextMatch;
while ((nextMatch = source.IndexOf(oldValue, startingPos, comparisonType)) > -1)
{
result.Append(source, startingPos, nextMatch - startingPos);
result.Append(newValue);
startingPos = nextMatch + oldValue.Length;
}
result.Append(source, startingPos, source.Length - startingPos);
return result.ToString();
}
Btw, here's also a similar Contains-method also taking a StringComparison:
[Pure]
public static bool Contains(this string source, string value, StringComparison comparisonType)
{
return source.IndexOf(value, comparisonType) >= 0;
}
Some tests:
[TestFixture]
public class ExternalTests
{
private static string[] TestReplace_args =
{
"ab/B/c/ac",
"HELLO World/Hello/Goodbye/Goodbye World",
"Hello World/world/there!/Hello there!",
"hello WoRlD/world/there!/hello there!",
"///",
"ab///ab",
"/ab/cd/",
"a|b|c|d|e|f/|//abcdef",
"a|b|c|d|e|f|/|/:/a:b:c:d:e:f:",
};
[Test, TestCaseSource("TestReplace_args")]
public void TestReplace(string teststring)
{
var split = teststring.Split("/");
var source = split[0];
var oldValue = split[1];
var newValue = split[2];
var result = split[3];
Assert.That(source.Replace(oldValue, newValue, StringComparison.OrdinalIgnoreCase), Is.EqualTo(result));
}
}
Here is my extension method, which combines Tom Beech's, with the recursiveness of sntbob's, and a cleaner fix to the bug that ksun pointed out.
Code:
public static string Replace(this string source, string oldString,
string newString, StringComparison comparison)
{
int index = source.IndexOf(oldString, comparison);
while (index > -1)
{
source = source.Remove(index, oldString.Length);
source = source.Insert(index, newString);
index = source.IndexOf(oldString, index + newString.Length, comparison);
}
return source;
}
Usage:
string source = "banana";
Console.WriteLine(source.Replace("AN", "banana", StringComparison.OrdinalIgnoreCase));
Result:
bbananabananaa
And, if you still want the recursive nature to be optional:
Code:
public static string Replace(this string source, string oldString,
string newString, StringComparison comparison,
bool recursive = true)
{
int index = source.IndexOf(oldString, comparison);
while (index > -1)
{
source = source.Remove(index, oldString.Length);
source = source.Insert(index, newString);
if (!recursive)
{
return source;
}
index = source.IndexOf(oldString, index + newString.Length, comparison);
}
return source;
}
Usage:
string source = "banana";
Console.WriteLine(source.Replace("AN", "banana", StringComparison.OrdinalIgnoreCase, false));
Result:
bbananaana
Utiltize StringComparison because of its handy OrdinalIgnoreCase
string sentence = "We know it contains 'camel' word.";
string wordToFind = "camel";
string replacementWord = "horse";
int index = sentence.IndexOf(wordToFind , StringComparison.OrdinalIgnoreCase)
// Did we match the word regardless of case
bool match = index >= 0;
// perform the replace on the matched word
if(match) {
sentence = sentence.Remove(index, wordToFind.Length)
sentence = sentence.Insert(index, replacementWord)
}
Sure would be nice if the C# String class had an ignoreCase() method like Java.
You could also use String.IndexOf
http://msdn.microsoft.com/en-us/library/system.string.indexof.aspx
You may get slightly better performance doing it this way than with RegExpressions (I abhor them because they're not intuitive and easy to screw up, although this simple .Net function call abstracts the actual messy RegEx, and doesn't provide much room for error), but that's probably not a concern for you; computers are REALLY fast these days, right? :) The overload for IndexOf that takes a StringComparison object allows you to optionally ignore case, and because IndexOf returns the first occurrence from at a specified position, you'll have to code a loop to process a string having multiple occurrences.
public static string CustomReplace(string srcText, string toFind, string toReplace, bool matchCase, bool replace0nce)
{
StringComparison sc = StringComparison.OrdinalIgnoreCase;
if (matchCase)
sc = StringComparison.Ordinal;
int pos;
while ((pos = srcText.IndexOf(toFind, sc)) > -1)
{
srcText = srcText.Remove(pos, toFind.Length);
srcText = srcText.Insert(pos, toReplace);
if (replace0nce)
break;
}
return srcText;
}
It may not be as efficient as some of the other answers, but I kind of like the CustomReplace function written by sntbob.
However, there is a flaw in it. If the text replacement is recursive it will cause an infinite loop. For example, CustomReplace("I eat bananas!","an","banana",false,false) would cause an infinite loop and the string would continue growing larger.
For example, after the 4th iteration the string would be "I eat bbbbbananaanaanaanaanas!"
If you want to only replace the two instances of "an" inside "banana" then you'll have to take another approach. I modified sntbob's code to account for this case. I admit that it's much more convoluted, but it handles recursive replacements.
public static string CustomReplace(string srcText, string toFind, string toReplace, bool matchCase, bool replaceOnce)
{
StringComparison sc = StringComparison.OrdinalIgnoreCase;
if (matchCase)
sc = StringComparison.Ordinal;
int pos;
int previousProcessedLength = 0;
string alreadyProcessedTxt = "";
string remainingToProcessTxt = srcText;
while ((pos = remainingToProcessTxt.IndexOf(toFind, sc)) > -1)
{
previousProcessedLength = alreadyProcessedTxt.Length;
//Append processed text up until the end of the found string and perform replacement
alreadyProcessedTxt += remainingToProcessTxt.Substring(0, pos + toFind.Length);
alreadyProcessedTxt = alreadyProcessedTxt.Remove(previousProcessedLength + pos, toFind.Length);
alreadyProcessedTxt = alreadyProcessedTxt.Insert(previousProcessedLength + pos, toReplace);
//Remove processed text from remaining
remainingToProcessTxt = remainingToProcessTxt.Substring(pos + toFind.Length);
if (replaceOnce)
break;
}
return alreadyProcessedTxt + remainingToProcessTxt;
}
Why not just import the Microsoft.VisualBasic namespace and use the VB Strings.Replace method?
https://msdn.microsoft.com/en-us/library/microsoft.visualbasic.strings.replace(v=vs.110).aspx
eg
var newString = Strings.Replace(SourceString, FindTextValue, ReplacementTextValue, 1, -1, Constants.vbTextCompare);
vbTextCompare forces a case-insensitive replacement. Job done.
Okay, it's not 'pure' C#, but it gets you to where you want to go with much less complexity and messing around.
Here's one more alternative that uses StringComparison as an extension method. on a StringBuilder object. I've read some articles indicating that a StringBuilder might be a little more efficient with memory than using strings. You can easily alter this to work with strings if that's what you need.
/// <summary>
/// Extension method to find/replace replaces text in a StringBuilder object
/// </summary>
/// <param name="original">Source StringBuilder object</param>
/// <param name="oldString">String to search for</param>
/// <param name="newString">String to replace each occurrance of oldString</param>
/// <param name="stringComparison">String comparison to use</param>
/// <returns>Original Stringbuilder with replacements made</returns>
public static StringBuilder Replace(this StringBuilder original,
string oldString, string newString, StringComparison stringComparison)
{
//If anything is null, or oldString is blank, exit with original value
if ( newString == null || original == null || string.IsNullOrEmpty(oldString))
return original;
//Convert to a string and get starting position using
//IndexOf which allows us to use StringComparison.
int pos = original.ToString().IndexOf(oldString, 0, stringComparison);
//Loop through until we find and replace all matches
while ( pos >= 0 )
{
//Remove the old string and insert the new one.
original.Remove(pos, oldString.Length).Insert(pos, newString);
//Get the next match starting 1 character after last replacement (to avoid a possible infinite loop)
pos = original.ToString().IndexOf(oldString, pos + newString.Length + 1, stringComparison);
}
return original;
}

Best practice for parsing and validating mobile number

I wonder what the best practice for parsing and validating a mobile number before sending a text is. I've got code that works, but I'd like to find out better ways of doing it (as my last question, this is part of my early new years resolution to write better quality code!).
At the moment we are very forgiving when the user enters the number on the form, they can enter things like "+44 123 4567890", "00441234567890", "0123456789", "+44(0)123456789", "012-345-6789" or even "haven't got a phone".
However, to send the text the format must be 44xxxxxxxxxx (this is for UK mobiles only), so we need to parse it and validate it before we can send. Below is the code that I have for now (C#, asp.net), it would be great if anyone had any ideas on how to improve it.
Thanks,
Annelie
private bool IsMobileNumberValid(string mobileNumber)
{
// parse the number
_mobileNumber = ParsedMobileNumber(mobileNumber);
// check if it's the right length
if (_mobileNumber.Length != 12)
{
return false;
}
// check if it contains non-numeric characters
if(!Regex.IsMatch(_mobileNumber, #"^[-+]?[0-9]*\.?[0-9]+$"))
{
return false;
}
return true;
}
private string ParsedMobileNumber(string number)
{
number = number.Replace("+", "");
number = number.Replace(".", "");
number = number.Replace(" ", "");
number = number.Replace("-", "");
number = number.Replace("/", "");
number = number.Replace("(", "");
number = number.Replace(")", "");
number = number.Trim(new char[] { '0' });
if (!number.StartsWith("44"))
{
number = "44" + number;
}
return number;
}
EDIT
Here's what I ended up with:
private bool IsMobileNumberValid(string mobileNumber)
{
// remove all non-numeric characters
_mobileNumber = CleanNumber(mobileNumber);
// trim any leading zeros
_mobileNumber = _mobileNumber.TrimStart(new char[] { '0' });
// check for this in case they've entered 44 (0)xxxxxxxxx or similar
if (_mobileNumber.StartsWith("440"))
{
_mobileNumber = _mobileNumber.Remove(2, 1);
}
// add country code if they haven't entered it
if (!_mobileNumber.StartsWith("44"))
{
_mobileNumber = "44" + _mobileNumber;
}
// check if it's the right length
if (_mobileNumber.Length != 12)
{
return false;
}
return true;
}
private string CleanNumber(string phone)
{
Regex digitsOnly = new Regex(#"[^\d]");
return digitsOnly.Replace(phone, "");
}
Use a regular expression to remove any non-numeric characters instead of trying to guess how a person will enter their number - this will remove all your Replace() and Trim() methods, unless you really need to trim a leading zero.
string CleanPhone(string phone)
{
Regex digitsOnly = new Regex(#"[^\d]");
return digitsOnly.Replace(phone, "");
}
Alternatively, I would recommend you use a masked textbox to collect the # (there are many options available) to allow only numeric input, and display the input with whatever format you'd like. This way you're guaranteeing that the value received will be all numeric characters.
Check out QAS, it's a commercial solution.
They have email, phone and address validations.
http://www.qas.com/phone-number-validation-web-service.htm
We use their services for Address and Email (not phone) and have been satisfied with it.
#annelie maybe you can update your regular expression to a more powerful one. Check out this site here. It contains many expressions but I think one of the top 2 expressions in the site should be suitable to you.
public class PhoneNumber
{
public PhoneNumber(string value)
{
if (String.IsNullOrEmpty(value))
throw new ArgumentNullException("numberString", Properties.Resources.PhoneNumberIsNullOrEmpty);
var match = new Regex(#"\+(\w+) \((\w+)\) (\w+)", RegexOptions.Compiled).Match(value);
if (match.Success)
{
ushort countryCode = 0;
ushort localCode = 0;
int number = 0;
if (UInt16.TryParse(match.Result("$1"), out countryCode) &&
UInt16.TryParse(match.Result("$2"), out localCode) &&
Int32.TryParse(match.Result("$3"), out number))
{
this.CountryCode = countryCode;
this.LocalCode = localCode;
this.Number = number;
}
}
else
{
throw new ArgumentNullException("numberString", Properties.Resources.PhoneNumberInvalid);
}
}
public PhoneNumber(int countryCode, int localCode, int number)
{
if (countryCode == 0)
throw new ArgumentOutOfRangeException("countryCode", Properties.Resources.PhoneNumberIsNullOrEmpty);
else if (localCode == 0)
throw new ArgumentOutOfRangeException("localCode", Properties.Resources.PhoneNumberIsNullOrEmpty);
else if (number == 0)
throw new ArgumentOutOfRangeException("number", Properties.Resources.PhoneNumberIsNullOrEmpty);
this.CountryCode = countryCode;
this.LocalCode = localCode;
this.Number = number;
}
public int CountryCode { get; set; }
public int LocalCode { get; set; }
public int Number { get; set; }
public override string ToString()
{
return String.Format(System.Globalization.CultureInfo.CurrentCulture, "+{0} ({1}) {2}", CountryCode, LocalCode, Number);
}
public static bool Validate(string value)
{
return new Regex(#"\+\w+ \(\w+\) \w+", RegexOptions.Compiled).IsMatch(value);
}
public static bool Validate(string countryCode, string localCode, string number, out PhoneNumber phoneNumber)
{
var valid = false;
phoneNumber = null;
try
{
ushort uCountryCode = 0;
ushort uLocalCode = 0;
int iNumber = 0;
// match only if all three numbers have been parsed successfully
valid = UInt16.TryParse(countryCode, out uCountryCode) &&
UInt16.TryParse(localCode, out uLocalCode) &&
Int32.TryParse(number, out iNumber);
if (valid)
phoneNumber = new PhoneNumber(uCountryCode, uLocalCode, iNumber);
}
catch (ArgumentException)
{
// still not match
}
return valid;
}
}

Categories