Entry box validation for IP address

# Purpose: a part of a series of examples of Tk code validating
# the input of a user.
#
# This example takes as an argument a possible IP address.
# Once this initial attempt to just limit the characters input
# and a bit of the format is working, perhaps someone can figure out
# whether it would be useful to add an alternative for 'existing ip
# address'.

# It currently doesn't work due to a problem figuring out how to impose
# the range values of the regular expression onto the values being entered
# by the user, so that one can only enter valid IP addresses.

proc checkip {ip} {
  set isIP "1"
 
  if {$ip == ""}                           then {set isIP "0"}
  if {[llength [split $ip .] ] != 4}       then {set isIP "0"}
  foreach i [split $ip .] {
   if {[string is integer $i] == 1} then {
    if {$i > 255} then {set isIP "0"}
    if {$i < 0}   then {set isIP "0"}
   } else { set isIP "0"
   }
  }

  set IPAddrRe {^(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])$} 

  if { $isIP  && ! [regexp -- $IPAddrRe $ip] } {
        set isIP "0"
  }
 
 return $isIP
}
pack [entry .e -validate focus -vcmd {checkip %P}]

The only problem with the above is that it doesn't work! by using the focus for validation it is too late to just return a boolean to cancel the edit because the entry is already editted. To do validation with focus, if there is a problem you must actual modify the value. It is much easier to validtae on each key & just not let the user type anything bad instead of letting him mess it up then telling him later it's bogus.

Try this one instead:

proc isIP {str} {
   set ok 1
   set parts [split $str .]
   if {[llength $parts] > 4} {
      set ok 0
   } else {
      if {"[lindex $parts end]" == ""} {
         # trailing dot, this is ok
         set parts [lrange $parts 0 end-1]
      } 
      foreach item $parts {
        
         if  {(   ([string length $item] > 1) && 
                  ([string equal [string range $item 0 0]] "0")
              ) ||
              (! [string is integer $item]) || 
              ($item < 0) || 
              ($item > 255)
         } {
            set ok 0
         }
      }
   }
   return $ok
}
pack [entry .e -validate all -vcmd {isIP %P}]

BBH


Readers should note that the second example doesn't attempt to do all that the first example did with respect to requiring only valid decimal values for the subportions of the ip address as far as I can figure... That may, or may not, be the case.


Actually it did check the same ranges as the first one, but it did have a flaw that it didn't check for leading 0's (which it now does) and I removed some cut/paste ghost from one line. The only thing it lacks is a final check at focus out for a complete ip (since now it allows partial match to let the user type it in. It would be easy to add, just pass a %V from the -vcmd option & add extra arg to func then do primarily the same checks except force the length to be exactly 4 and don't do the trick of letting the last one blank. If ti isn't complete you could flag it with a color change, or pop up a box letting user know it wasn't done. - BBH


The more I look at it the RE method would be simpler, and easier to put specific ranges on any portion of the IP here is tonights thoughts - BBH

proc isIP {str type} {
   # modify these if you want to check specifi ranges for
   # each portion - now it look for 0 - 255 in each
   set ipnum1 {\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5]}
   set ipnum2 {\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5]}
   set ipnum3 {\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5]}
   set ipnum4 {\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5]}
   set fullExp {^($ipnum1)\.($ipnum2)\.($ipnum3)\.($ipnum4)$}
   set partialExp {^(($ipnum1)(\.(($ipnum2)(\.(($ipnum3)(\.(($ipnum4)?)?)?)?)?)?)?)?$}
   set fullExp [subst -nocommands -nobackslashes $fullExp]
   set partialExp [subst -nocommands -nobackslashes $partialExp]
   if [string equal $type focusout] {
      if [regexp -- $fullExp $str] {
         return 1
      } else {
         tk_messageBox -message "IP is NOT complete!"
         return 0
      }
   } else {
      return [regexp -- $partialExp $str]
   }
}
pack [entry .e -textvar IP -validate all -vcmd "isIP %P %V"]

RS 2013-09-19 Here is my take on the topic: four entries, with fixed dots in between:

proc ip_entry {w args} {
    frame $w -relief sunken -borderwidth 2
    entry $w.1 -width 3 -relief flat -validate all -vcmd {is_valid %P}
    label $w.a -text . -background gray90 -relief flat
    entry $w.2 -width 3 -relief flat -validate all -vcmd {is_valid %P}
    label $w.b -text . -background gray90 -relief flat
    entry $w.3 -width 3 -relief flat -validate all -vcmd {is_valid %P}
    label $w.c -text . -background gray90 -relief flat
    entry $w.4 -width 3 -relief flat -validate all -vcmd {is_valid %P}
    eval pack [winfo children $w] -side left -padx 0
    return $w
}
proc is_valid str {
    if {$str eq ""} {return 1}
    expr {[string is integer -strict $str] && $str >= 0 && $str < 256}
}