tags:

views:

181

answers:

5

I'm not sure how to accomplish this with a regular expression (or if I can; I'm new to regex). I have an angle value the user will type in and I'm trying to validate the entry. It is in the form degrees-minutes-seconds. The problem I'm having, is that if the user mistypes the seconds portion, I have to catch that error, but my match for degrees-minutes is a success.

Perhaps the method will explain better:

    private Boolean isTextValid(String _angleValue) {

        Regex _degreeMatchPattern = new Regex("0*[1-9]");
        Regex degreeMinMatchPattern = new Regex("(0*[0-9]-{1}0*[0-9]){1}");
        Regex degreeMinSecMatchPattern = new Regex("0*[0-9]-{1}0*[0-9]-{1}0*[0-9]");

        Match _degreeMatch, _degreeMinMatch, _degreeMinSecMatch;

        _degreeMinSecMatch = degreeMinSecMatchPattern.Match(_angleValue);
        if (_degreeMinSecMatch.Success)
            return true;

        _degreeMinMatch = degreeMinMatchPattern.Match(_angleValue);
        if (_degreeMinMatch.Success)
            return true;

        _degreeMatch = _degreeMatchPattern.Match(_angleValue);
        if (_degreeMatch.Success)
            return true;

        return false;
    }
}

I want to check for degrees-minutes if the degrees-minutes-seconds match is unsuccessful, but only if the user didn't enter any seconds data. Can I do this via regex, or do I need to parse the string and evaluate each portion separately? Thanks.

EDIT: Sample data would be 45-23-10 as correct data. The problem is 45-23 is also valid data; the 0 seconds is understood. So if the user types 45-23-1= on accident, the degreeMinMatchPattern regex in my code will match succesfully, even though it is invalid.

Second EDIT: Just to make it clear, the minutes and second portions are both optional. The user can type 45 and that is valid.

+1  A: 

Maybe you would do better to just parse the input out and check is piece separately.

Steven Sudit
A: 

I'm not sure I understand correctly, but I think

(?<degrees>0*[0-9])-?(?<minutes>0*[0-9])(?:-?(?<seconds>0*[0-9]))?$

might work.

But this is quite ambiguous; also I'm wondering why you're only allowing single-digit degree/minute/second values. Please show some examples you do and don't want to match.

Tim Pietzcker
+4  A: 

You can specify "this part of the pattern must match at least 3 times" using the {m,} syntax. Since there are hyphens between each component, specify the first part separately, and then each hyphen-digit combination can be grouped together after:

`[0-9](-[0-9]){2,}`

You also can shorten [0-9] to \d: \d(-\d){2,}

Ether
I think he wants to match 2 *or* 3 numbers, but 2 numbers only if nothing follows, not if some invalid input follows.
Tim Pietzcker
+2  A: 

First off, a character in a regex is matched once by default, so {1} is redundant.

Second, since you can apparently isolate this value (you prompt for just this value, instead of having to look for it in a paragraph of entered data) you should include ^ and $ in your string, to enforce that the string should contain ONLY this pattern.

Try "^\d{1,3}-\d{1,2}(-\d{1,2})?$".

Breaking it down: ^ matches the beginning of the string. \d matches any single decimal character, and then behind that you're specifying {1,3} which will match a set of one to three occurrences of any digit. Then you're looking for one dash, then a similar decimal pattern but only one or two times. The last term is enclosed in parenthesis so we can group the characters. Its form is similar to the first two, then there's a ? which marks the preceding character group as optional. The $ at the end indicates that the input should end. Given this, it will match 222-33-44 or 222-33, but not 222-3344 or 222-33-abc.

Keep in mind there are additional rules you might want to incorporate. For instance, seconds can be expressed as a decimal (if you want a resolution smaller than one second). You would need to optionally expect the decimal point and one or more additional digits. Also, you probably have a maximum degree value; the above regex will match the maximum integer DMS value of 359-59-59, however it will also match 999-99-99 which is not valid. You can limit the maximum value using regex (for example "(3[0-5]\d|[1-2]\d{2}|\d{1,2})" will match any number from 0 to 359, by matching a 3, then 0-5, then 0-9, OR any 3-digit number starting with 1 or 2, OR any two-digit number), but as the example shows the regex will get long and messy, so document it well in code as to what you're doing.

KeithS
With a minor modification (as the second of the three is also optional, and C# requires a second backslash to properly escape the d) this worked. My final expression is "^\\d{1,3}(-\\d{1,2}(-\\d{1,2})?)?$" Thanks!
Shawn
If you put an @ in front of the double quote that opens the string, .NET will automatically escape any characters that need it. This is useful for files (backslash directory separators) and multiline data (\r\n everywhere) as well as for regexes.
KeithS
And I learned something else today too (I knew about the multiline data, but not the regex). Thanks!
Shawn
A: 

Maybe you should to try something like this and test for empty/invalid groups:

Regex degrees = new Regex(
                @"(?<degrees>\d+)(?:-(?<minutes>\d+))?(?:-(?<seconds>\d+))?");
string[] samples = new []{ "123", "123-456", "123-456-789" };
foreach (var sample in samples)
{
    Match m = degrees.Match(sample);
    if(m.Success)
    {
        string degrees = m.Groups["degrees"].Value;
        string minutes = m.Groups["minutes"].Value;
        string seconds = m.Groups["seconds"].Value;
        Console.WriteLine("{0}°{1}'{2}\"", degrees,
            String.IsNullOrEmpty(minutes) ? "0" : minutes,
            String.IsNullOrEmpty(seconds) ? "0" : seconds
        );
    }
}
Rubens Farias