Ekstraopgaver til Løbeseddel 5: Streng-matching

for Databasestøttet Webpublicering

af Martin Elsman og Niels Hallenberg sidst rettet 15. februar, 2002


Dette ekstrasæt til løbeseddel 5 indeholder to opgaver.

Mønstre

I denne opgave skal du opskrive forskellige mønstre til brug ved simpel pattern-matching med regexp-kommandoen.

Angiv mønstre for

  1. reelle tal. Vi definerer et reelt tal til en streng der opfylder mindst et af nedenstående:

    Vink:Dit mønster skal tage højde for to muligheder: (1) tvungen tal til venstre for . og mulighed for tal til højre. (2) tvungen tal til højre for . og mulighed for tal til venstre. Du angiver valgmuligheder med |. Et tvungen tal kan f.eks. laves med [0-9]+. Et valgfrit tal kan f.eks. laves med [0-9]*. Punktum er altid valgfri, dvs. du kan anvende \.? til at matche punktum.

    Følgende strenge skal genkendes: "1", "1.", "1.0", ".1", "1032.343", ".04330" og ".3".

    Følgende strenge skal ikke genkendes: "1a2", ".", "32,23", "a.3" og "to.3".

  2. url, defineret ved sekvensen af tegn startende med http:// efterfulgt af en ikke tom sekvens af tegn fra det engelske alfabet, cifre, ., / og _.

    Følgende strenge skal genkendes: "http://a", "http://4_a" og "http://a/aa.html".

    Følgende strenge skal ikke genkendes: "", "http://", "http://a€#" og "http://aø.tcl".

Det anbefales at regexp-kommandoen bruges til at teste mønstrene.

Ugedag

Denne opgave går ud på at konstruere en web-service, som ved indtastning af en dato i et form-felt returnerer en side indeholdende ugedagen for den indtastede dato.

I den første del af opgaven skal du konstruere et regexp-mønster (pattern) til matching af datoer på formen

DD.MM.YYYY

hvor hvert bogstav D, M og Y står for et tal mellem 0 og 9. De første to cifre udgør dagen i datoen, de næste to cifre udgør måneden og de sidste fire cifre udgør året. Her er nogle eksempler på strenge for hvilke regexp-kommandoen skal returnere 1 (for success) og hvorefter variablerne dd, mm, og yyyy bliver sat til de respektive tal:

% regexp $date_pattern "24.12.2000" match dd mm yyyy
1
% set dd
24
% set mm
12
% set yyyy
2000
% regexp $date_pattern "12.53.1999" match dd mm yyyy
1
% set dd
12
% set mm
53
% set yyyy
1999
Bemærk at mønsteret ikke skal sikre at datoen er egentlig eksisterende! Her er nogle eksempler på strenge for hvilke regexp-kommandoen skal returnere 0 (for fejl):
% regexp $date_pattern "24.12.00" match dd mm yyyy
0
% regexp $date_pattern "10.5.1999" match dd mm yyyy
0
Det er altså din opgave at konstruere mønsteret i variablen date_pattern. Løsningen skal bruges senere i opgaven.

Du skal nu i den resterende del af opgaven konstruere en webservice hvor en bruger kan indtaste en dato i et form-felt og få svar på hvilken ugedag datoen falder på. Løsningen skal bestå af to filer placeret på hug.it.edu i biblioteket /web/login/www/oevelse5/:

weekday.html:

Denne fil skal indeholde HTML kode for en form med mulighed for indtastning af en dato i et form-felt. Efter indtastning af en dato i form-feltet skal filen weekday.tcl aktiveres med en form-variabel, navngivet date, indeholdende den indtastede dato.

weekday.tcl:

Denne fil skal indeholde en del procedurer, som tilsammen gør det muligt at beregne ugedagen hørende til en dato. Start med at kopiere følgende procedurer ind i filen weekday.tcl:
  # --------------------------------------------------------------------
  # Return 1 if year is a leap year, zero otherwise
  # --------------------------------------------------------------------
  proc date_leap { y } {
      return [expr ($y % 4 == 0) && ($y % 100 != 0) || ($y % 400 == 0)]
  }

  # --------------------------------------------------------------------
  # Return the number of leap years since year 0 and before year y
  # --------------------------------------------------------------------
  proc date_preceeding_leaps { y } {
      if { $y == 0 } {
          return 0
      } else {
          incr y -1
          return [expr 1 + $y / 4 - $y / 100 + $y / 400]
      }
  }

  # --------------------------------------------------------------------
  # Return the number of days in a given month; uses year to deal with
  # leap days
  # --------------------------------------------------------------------
  proc date_days_in_month { year month } {
      switch -regexp $month {
          ^(1|3|5|7|8|10|12)$  { return 31 }
          ^(4|6|9|11)$         { return 30 }
          ^2$                  { if [date_leap $year] { 
                                    return 29 
                                 } else { 
                                    return 28 
                                 } 
                               }
          default              { return -1 }
      }
  }

  # --------------------------------------------------------------------
  # Return 1 if a date is valid, 0 otherwise
  # --------------------------------------------------------------------
  proc date_ok { dd mm yyyy } {
      return [expr ($mm >= 1) && ($mm <= 12) && ($dd >= 1) && ($dd <= [date_days_in_month $yyyy $mm])]
  } 

  # --------------------------------------------------------------------
  # Given a date (dd, mm, yyyy), return the number of days since 
  # 01.01.0000
  # --------------------------------------------------------------------
  proc date_preceeding_days { dd mm yyyy } {
      # days in last month
      set days [expr $dd - 1]

      # days in preceeding months
      incr mm -1
      while { $mm >= 1 } {
          incr days [date_days_in_month $yyyy $mm]
          incr mm -1
      }
      # days in preceeding years
      incr days [expr 365 * $yyyy + [date_preceeding_leaps $yyyy]]

      return $days
  }

  # --------------------------------------------------------------------
  # Given a number between 0 and 6, return a weekday, starting with
  # Saturday (01.01.0000 was a Saturday!)
  # --------------------------------------------------------------------
  proc date_pr_day { i } {
      switch $i {
          0  {return Saturday}
          1  {return Sunday}
          2  {return Monday}
          3  {return Tuesday}
          4  {return Wednesday}
          5  {return Thursday}
          6  {return Friday}
          default {return "WRONG DAY"}
      }
  }

  # --------------------------------------------------------------------
  # remove leading zeros from a number; fails if the argument is
  # not a number.
  # --------------------------------------------------------------------
  proc remove_leading_zeros { n } {
      if { [regexp {^0*([1-9][0-9]*)$} $n match res] == 0 } {
          regexp {^0*(0)$} $n match res
      }
      return $res
  }
Efterfølgende i filen weekday.tcl skal du indsætte nedenstående procedure, hvor du har indsat dit dato-mønster, som du konstruerede tidligere i opgaven:
  # --------------------------------------------------------------------
  # Given a date in the form DD.MM.YYYY, return the weekday; if date
  # is not valid, return -1
  # --------------------------------------------------------------------
  proc weekday { date } {
      # the date pattern to use
      set date_pattern {<==INDSÆT DIT DATO-MØNSTER HER==>}

      # return -1 if matching fails
      if { ![regexp $date_pattern $date match dd mm yyyy] } {
          return -1
      }

      # remove leading zeros from dd, mm, and yyyy; tcl treats numbers
      # with leading zeros as octal numbers!
      set dd [remove_leading_zeros $dd]
      set mm [remove_leading_zeros $mm]
      set yyyy [remove_leading_zeros $yyyy]

      # return -1 if date is not valid
      if { ![date_ok $dd $mm $yyyy] } {
          return -1
      }
      # calculate the number of preceeding days since 01.01.0000
      set preceeding_days [date_preceeding_days $dd $mm $yyyy]

      return [date_pr_day [expr $preceeding_days % 7]]
  }
Du mangler nu kun tilslut i weekday.tcl filen at indsætte et kald til set_form_variables proceduren, hvorefter weekday-proceduren skal kaldes med datoen fra form-feltet som argument. Indsæt sidst i filen weekday.tcl kode til at returnere (med ns_return) HTML-kode indeholdende resultatet af at kalde weekday-proceduren.

Indsæt et link til servicen fra din index.html side på hug.it.edu.

Vejledende løsning:

ugedag_form ugedag_res


mael@it.edu, nh@it.edu