Eksamen, forår 2001

for Database-baseret Web-publicering, forår 2001

af Niels Hallenberg


Introduktion

I dette eksamenssæt skal du implementere dele af en webbaseret nyhedsliste. Nyhedslisten ligner meget den, der anvendes på IT-højskolen i København (IT-C).

Ideen med nyhedslisten er, at personer, som er tilmeldt listen, automatisk får tilsendt emails, når IT-C annoncerer nyheder. Dette kan eksempelvis være stillingsopslag, nye uddannelser og Buzz Talks. Når personer tilmelder sig listen, vælger de, hvilke typer af informationer de er interesserede i, f.eks. uddannelser og Buzz Talks. Udsendelse af emails sker ved, at en medarbejder ved IT-C går ind på en administrationsside, hvori emailen indtastes og sendes.

Opgave 1 (20 procent) - HTML

Nedenfor vises indgangssiden for den web-baserede nyhedsliste:

Indgangssiden indeholder tre formularer (HTML-tag <form ...>) svarende til de tre kasser: "Tilmelding", "Kontrol" og "Afmelding". Siden giver mulighed for, at personer kan

  1. Tilmelde sig nyhedslisten ved bl.a. at angive email og de informationstyper (Buzz Talk, Forskning, Stillinger, Uddannelser), man er interesseret i. Man kan angive 1 eller flere informationstyper.
  2. Kontrollere sin tilmelding, ved at indtaste sin email. Kontrollen viser bl.a., hvilke informationstyper man har registreret.
  3. Afmelde sig fra nyhedslisten.

Opgave 1.1

Opskriv HTML koden for formularen "Kontrol" i siden ovenfor. Besvarelsen skal udelukkende indholde HTML-koden for formularen og ikke HTML koden for hele siden:
  <form method=post action=nl_kontrol.tcl>
    ...
  </form>
Kassen med teksten "Kontrol" er ikke en del af formularen.

Der er ingen skjulte formvariable i formularen.

Formularen indeholder to form-elementer:

  1. Et indtastningsfelt (<input>) til email, hvor form variablen navngives email.
  2. En submit-knap hvor knappen har teksten "Kontrol".

Opgave 1.2

Opskriv HTML koden for formularen "Tilmelding" i siden ovenfor. Besvarelsen skal udelukkende indeholde HTML-koden for formularen og ikke HTML-koden for hele siden:
  <form method=post action=nl_tilmeld.tcl>
    ...
  </form>
Den sorte ramme er ikke en del af formularen.

Der er ingen skjulte formvariable i formularen.

Formularen indeholder følgende fem form-elementer:

  1. Et indtastningsfelt til navn, hvor form variablen navngives name.
  2. Et indtastningsfelt til telefon, hvor form variablen navngives phone.
  3. Et indtastningsfelt til email, hvor form variablen navngives email.
  4. Fire knapper til informationstyperne der alle navngives info. Værdierne af hver knap sættes til henholdsvis 1 for Uddannelser, 2 for Stillinger, 3 for Forskning og 4 for Buzz Talks. Man skal kunne klikke (vælge) flere knapper samtidigt.
  5. En submit-knap hvor knappen har teksten "Tilmeld mig".

Opgave 1.3

I denne opgave skal du skrive en procedure nl_return_page, som returnerer HTML-koden, der omgiver formularerne på indgangssiden. Procedureren tager to argumenter title og body. Indgangssiden kan genereres ved at kalde nl_return_page med title sat lig "IT-højskolens nyhedstjeneste" og body sat lig de tre formularer (jvf. billedet med indgangssiden vist ovenfor):
  proc nl_return_page { title body } {
    ns_return 200 text/html "
      <html>
         ##A##
     </html>"
  }
Der er følgende krav til siden: Bemærk, at den sorte ramme rundt om hele siden ikke er en del af den HTML-kode, som du skal skrive.

Du skal opskrive kode for programpunktet ##A##.

Opgave 2 (20 procent) - SQL

Datamodellen for nyhedslisten består af tre tabeller:
  1. nl_user - indeholder data om de tilmeldte personer
  2. nl_info - indeholder de mulige informationstyper, dvs. Buzz Talk, Forskning, Stillinger og Uddannelser
  3. nl_user_info - indeholder informationstyper knyttet til de tilmeldte personer
Datamodellen er tegnet nedenfor. De firkantede kasser angiver tabellerne. En tabels navn er skrevet øverst, og tabellens felter er angivet under tabellens navn. En-til-mange relationer er angivet med kragefødder; eksempelvis kan en person i nl_user have 1 eller flere informationstyper i tabellen nl_user_info.

Udover de tre tabeller anvendes en Oracle sekvens til at generere unikke numre: nl_user_seq. Tabellen nl_info og Oracle sekvensen til generering af unikke numre er oprettet ved følgende SQL kommandoer:

  create sequence nl_user_seq start with 5;

  create table nl_info (
    info_id integer primary key,
    info_name varchar(40) unique not null
  );
Følgende SQL insert kommandoer kan efterfølgende antages at være udført:
  insert into nl_info (info_id, info_name) values (1,'Uddannelser');
  insert into nl_info (info_id, info_name) values (2,'Stillinger');
  insert into nl_info (info_id, info_name) values (3,'Forskning');
  insert into nl_info (info_id, info_name) values (4,'Buzz Talks');

Opgave 2.1

Vis resultatet af at udføre følgende select kommando:
  select info_id, info_name from nl_info order by info_name;

Opgave 2.2

Opskriv en SQL kommando til at oprette tabellen nl_user. Tabellen skal have fem felter (kolonner): Opskriv en SQL kommando til at oprette tabellen nl_user_info. Tabellen skal have to felter:

Opgave 2.3

Skriv en SQL kommando til indsættelse af følgende person i tabellen nl_user: navn "Anders And", telefon "34 32 56 43" og email "anders@andeby.dk". Sæt user_id lig 1 og create_date lig dagsdato.

Skriv en SQL kommando som knytter personen Anders And (med user_id lig 1) til informationstypen Buzz Talks (info_id lig 4).

Opgave 2.4

Konstruer en SQL kommando til udtrækning af alle personer, som skal modtage nyheder, der vedrører informationstypen "Uddannelser" (info_id lig 1).

Følgende kolonner skal indgå i resultatet: name, email, info_name, user_id og info_id.

Rækkerne ønskes sorteret stigende efter email.

Vink: SQL kommandoen skal hente data fra de tre tabeller nl_user, nl_user_info og nl_info.

Opgave 2.5

Konstruer en SQL kommando, som returnerer antallet af personer, der har tilmeldt sig hver informationstype.

Kolonnerne i resultatet skal være name og count (dvs. antallet af personer tilmeldt den pågældende informationstype). Resultatet skal være sorteret stigende efter kolonnen name.

SQL kommandoen kan eksempelvis returnere:

       COUNT NAME
  ---------- -----------
           2 Buzz Talks
           3 Forskning
           3 Stillinger
           3 Uddannelser
Der er to personer, som har tilmeldt sig Buzz Talks osv.

Vink: SQL kommandoen skal hente data fra de to tabeller nl_info og nl_user_info og desuden benytte SQL's group by konstruktion.

Opgave 3 (20 procent) - Tcl

Opgave 3.1

Vis HTML-koden, som er resultatet af at køre Tcl-koden vist nedenfor (dvs. HTML-koden som returneres ved kaldet til gen_info_buttons).

Indholdet af tabellen nl_info er som i opgave 2.1.

  proc gen_info_buttons { db group_name } {
    set query "select info_id, name from nl_info order by name"
    set selection [ns_db select $db $query]

    set res "<blockquote>\n"
    while {[ns_db getrow $db $selection]} {
      set_variables_after_query
    
      append res "<input type=checkbox name=$group_name 
                  value=\"$info_id\"> $name<br>\n"
    }
    append res "</blockquote>\n"
    return $res
  }

  set db [ns_db gethandle]
  gen_info_buttons $db "info"
Bemærk: Hvis du ikke har løst opgave 2.1, så skal du kigge på insert-kommandoerne lige før opgave 2.1.

Opgave 3.2

Betragt listen counts
  set counts [list 2 3 2 3]
Hvad er resultatet af at udføre Tcl-koden nedenfor (dvs. hvad returnerer kaldet til calc_sum).
  proc calc_sum { counts } {
    set sum 0.0
    foreach i $counts {
      set sum [expr $sum + $i]
    }
    return $sum
  }
  calc_sum $counts
Du skal angive resultatet med 1 decimal.

Opgave 3.3

Vi betragter igen listen counts fra opgave 3.2 samt listen names:
  set names [list "Buzz Talks" "Forskning" "Stillinger" "Uddannelser"]
Du skal skrive en procedure calc_pct, som givet listerne counts og names som argument returnerer følgende HTML-kode (der må gerne være et vilkårligt antal decimaler før %-tegnet):
  <ul>
    <li><b>Buzz Talks</b>: 2(20.00%)
    <li><b>Forskning</b>: 3(30.00%)
    <li><b>Stillinger</b>: 2(20.00%)
    <li><b>Uddannelser</b>: 3(30.00%)
  </ul>
Det første element i listen counts (2) er antallet af tilmeldinger til første informationstype i listen names (Buzz Talks) osv.

For hver informationstype angives i procent antallet af tilmeldinger ud af samtlige tilmeldinger; eksempelvis er 2 ud af i alt 10 tilmeldinger 20.00% (2 / 10 * 100 = 20.00).

Du kan anvende følgende skabelon:

proc calc_pct { counts names } {
  set sum [calc_sum $counts]

  set infos "<ul>\n"
  for {set i 0} {$i < [llength $counts]} {incr i} {
    append infos "<li><b>[lindex $names $i]</b>: "
    ##A##
  }
  append infos "</ul>\n"

  return $infos
}
Du skal opskrive kode for programpunktet ##A##.

Opgave 4 (10 procent) - Regulære udtryk

Opgave 4.1

Betragt det regulære udtryk
^[1-9][0-9]*$

Hvilke af følgende fem strenge matches af det regulære udtryk ovenfor:

Opgave 4.2

På nyhedslisten definerer vi et telefonnummer til at være en måske tom sekvens af tal, der gerne må være adskilt af mellemrum og - (bindestreg).

Følgende strenge er gyldige telefonnumre:

Følgende strenge er ikke gyldige telefonnumre: Opskriv et regulært udtryk, der matcher telefonnumre, som defineret ovenfor.

Vink: Der er ikke nogen øvre grænse på længden af et telefonnummer.

Opgave 5 (30 procent) - Web-service

Til konstruktion af Tcl-filerne, som implementerer nyhedslisten, er der opstillet tilstandsdiagrammet på side 11. Kasserne i tilstandsdiagrammet repræsenterer web-sider og pilene repræsenterer de Tcl-programmer, der afvikles for at komme fra en web-side til en anden.

Opgave 5.1

Nedenfor følger en skabelon til filen nl_tilmeld.tcl, som tilmelder en ny person til nyhedslisten.

Formvariablene name, phone, email og info modtages fra tilmeldingsformularen på indgangssiden.

Proceduren set_form_var_as_list returnerer en liste med de informationstyper, som brugeren har valgt på indgangssiden. Hvis informationstyperne Buzz Talks og Uddannelser er valgt, så returneres listen (svarende til info_id i tabellen nl_info):

  [list 4 1]
Vi har simplificeret opgaven ved ikke at checke formvariable; vi checker dog, at der mindst er valgt en informationstype.

  # expecting form variables: name, phone, email and info (a list)
  set_the_usual_form_variables

  set_form_var_as_list info

  if {[llength $info] == 0} {
    nl_return_page "Tilmelding" "Du skal angive mindst en informationstype 
                    før du kan tilmeldes."
    exit
  }

  set db [ns_db gethandle]
  # Generate fresh user_id
  set user_id [database_to_tcl_string ##A##]

  set insert_sql "insert into nl_user ##B##"

  if {[catch { ns_db dml $db $insert_sql } errmsg]} {
    # the insert went wrong; assume it is because the 
    # email is already in the user table
    nl_return_page "Tilmelding" "Vi kan ikke registrere din tilmelding 
                    idet den indtastede email allerede er registreret."
  } else { 
    foreach info_id $info {
      set insert_sql ##C##
      ns_db dml $db $insert_sql
    }
    nl_return_page "Tilmelding" "Tak for din tilmelding."
  }
Opgaven består i at opskrive kode for programpunkterne ##A##, ##B## og ##C## i skabelonen ovenfor.

Opgave 5.2

I denne opgave skal du konstruere dele af filen nl_kontrol.tcl, som viser registreret information om allerede tilmeldte personer.

Formvariablen email modtages fra kontrolformularen på indgangssiden. Vi har simplificeret opgaven ved ikke at checke formvariablen.

  # expecting form variable: email
  set_the_usual_form_variables

  set db [ns_db gethandle]
  set query "select user_id, name, phone, create_date from nl_user 
              where nl_user.email = '$QQemail'"
  set selection [ns_db 0or1row $db $query]
 
  set body ""
  if { $selection == "" } {
    nl_return_page "Kontrol" "Den indtastede email er ikke 
                    registreret i nyhedstjenesten."
  } else {
    set_variables_after_query

    append body "
      <b>$name</b> er registreret den $create_date med <p>
      email: <b>$email</b>, <p>
      telefon: <b>$phone</b> og følgende informationstyper:<p>
      <ul>\n"    
  
    set query "select nl_info.info_name from nl_user_info, nl_info 
                where nl_user_info.info_id = nl_info.info_id 
                  and nl_user_info.user_id = '$user_id'
                order by nl_info.info_name"

    ##A##
 
    append body "</ul>"
    nl_return_page "Kontrol" "$body"
  }
Du skal opskrive kode for programpunktet ##A##, som skal tilføje HTML-kode til variablen body, således at body også indeholder en "bullet"-liste (tag <ul>) med de informationstyper som brugeren er tilmeldt.

Vink: Kommandoen ns_db 0or1row udfører SQL-forespørgslen (query) og returnerer enten 0 eller 1 række. Da email er unik, så kan der højst returneres 1 række. Hvis der returneres 0 rækker, så er selection lig den tomme streng ("").

Opgave 5.3

I denne opgave skal du konstruere filen nl_afmeld.tcl, som sletter en person fra databasen.

Formvariablen email modtages fra afmeldingsformularen på indgangssiden. Vi simplificerer opgaven ved ikke at checke formvariablen.

  # expecting form variable: email
  set_the_usual_form_variables

  set db [ns_db gethandle]

  set query "select user_id from nl_user where email = '$QQemail'"

  ##A##
Du skal opskrive kode for programpunktet ##A##, som skal se, om personen findes i tabellen nl_user. Hvis dette ikke er tilfældet, returneres en fejl side (med nl_return_page, Opgave 1.3), ellers slettes personen fra tabellen nl_user og nl_user_info og der returneres en side med information om, at personen er slettet (med nl_return_page).

Vink: Til at finde ud af om personen allerede findes i nl_user, kan du f.eks. anvende Tcl kommandoerne database_to_tcl_string og catch.

Opgave 5.4

I denne opgave skal du konstruere dele af filen nl_stat.tcl.

Der overføres ikke nogen formvariable til nl_stat.tcl.

  proc calc_sum { counts } {
    -- Som vist i opgave 3.2
  }

  proc calc_pct { counts names } {
     -- Dit svar i opgave 3.3
  }

  set query "-- Dit svar i opgave 2.5"

  set db [ns_db gethandle]
  set selection [ns_db select $db $query]

  ##A##

  nl_return_page "Statistik" "
    Af de registrerede person på nyhedslisten fordeler interessen sig således:
    <blockquote>
    [calc_pct $counts $names]
    </blockquote>"
Du skal opskrive kode for programpunktet ##A##, som går ud på at opbygge to lister: counts og names svarende til listerne i opgave 3.3.

Vink: Du skal anvende en løkke (Tcl kommandoerne for eller while) samt Tcl kommandoen lappend.

Opgave 5.5

Du skal skrive en Tcl-fil nl_send.tcl, som givet en informationstype og indhold af en email, sender emailen til alle personer på nyhedslisten, som er tilknyttet informationstypen.

Filen nl_send.tcl modtager formvariablene info, subject, content og sender.

Vi simplificerer opgaven ved ikke at checke formvariablene samt antager, at der kun modtages en informationstype.

  # Expect form variables: sender, subject, content and info
  set_the_usual_form_variables

  set query "-- Dit svar på opgave 2.4 med info_id = 1
             -- erstattet af info_id = '$info_id'";

  ##A##
Du skal opskrive kode til programpunktet ##A##, der med Tcl-kommandoen qmail (eller ns_sendmail) sender en email til de personer, som forespørgslen returnerer.

Der skal returneres en side til brugeren, som viser navn og email på dem, der er sendt en email til.


nh@it.edu