Soluzione HomeWork6aa0203

Vedi anche: HomeWork6aa0203, DomandeHomework6aa0203, RisultatiHomework6aa0203.

Prima versione

Questo č un PRIMO ABBOZZO della soluzione dell'esercizio 6:

  • non l'ho testato a fondo frown
  • fa il sort ma non la gestione degli errori
Sto finendo la versione completa.
#include <stdio.h>
#include <stdlib.h>

/*
    ANSI C vuole che tutte le struct abbiano nome
*/
typedef struct {
    char nome[31];
    char cognome[31];
    int  g;
    int  m;
    int  a;
    int hasEmail;      /* se TRUE allora c'e' l'email */
    union {
   struct {
       char uname[51];
       char domain[51];
       } email;      /* email      */
   struct {
       int pref_int;
       char prefisso[6];
       int numero;
   } tel;          /* telefono       */
    } et;          /* email o telefono    */
} Voce;

/* prototipi delle funzioni */
int fine(    char riga[]);
int add(    char riga[], Voce agenda[], int quanti);
int del(    char riga[], Voce agenda[], int quanti);
int print(    char riga[], Voce agenda[], int quanti);
int prox(    char riga[], Voce agenda[], int quanti);
int sort_nome(    char riga[], Voce agenda[], int quanti);
int sort_cognome(char riga[], Voce agenda[], int quanti);
int sort_compl(    char riga[], Voce agenda[], int quanti);

void print_voce(Voce * voce);

#define NVOCI 20
#define MAXLINE 201

/*
    Programma principale
*/
int main(int argc, char* argv[]) {

    char riga[MAXLINE] = { 0 };   /* buffer per l'ultima riga letta    */
    Voce agenda[NVOCI] = { 0 };   /* vettore di NVOCI (20) voci       */
    int quanti = 0;      /* numero di voci inserite      */
    
    /* leggo tutta una riga (al max MAXLINE caratteri) */
    scanf("%200[^\n]",riga);
    
    /* continuo finche' non arriva il comando 'fine' */
    while (!fine(riga)) {
   if (add(riga,agenda,quanti))
       if (quanti<NVOCI) 
      quanti---++;
   quanti -= del(riga,agenda,quanti);
   prox(riga,agenda,quanti);
   print(riga,agenda,quanti);
   sort_nome(riga,agenda,quanti);
   sort_cognome(riga,agenda,quanti);
   sort_compl(riga,agenda,quanti);
   scanf("\n%200[^\n]",riga);
    }

    return 0;
}

/*
    Riconosco il comando 'fine'
    torno TRUE se l'ho riconosciuto
*/
int fine(    char riga[]) 
{
    return (0==strcmp(riga,"fine"));
}

/*
    Riconosco ed eseguo i comandi 
   'add <nome> <cognome> <data> tel <telefono>'
   'add <nome> <cognome> <data> email <email>'
    torno TRUE se l'ho riconosciuto
*/
int add(    char riga[], Voce agenda[], int quanti)
{
    Voce nuova = { 0 };
    char resto[MAXLINE] = { 0 };
    
    /* prima riconosciamo nome e cognome e data di nascita */
    if (6 != sscanf(riga,"add \"%30[^\"\n]\" \"%30[^\"\n]\" %2d-%2d-%4d %[^\n]",
      nuova.nome, nuova.cognome, &nuova.g, &nuova.m, &nuova.a, resto))
    if (6 != sscanf(riga,"add \"%30[^\"\n]\" %30s %2d-%2d-%4d %[^\n]",
      nuova.nome, nuova.cognome, &nuova.g, &nuova.m, &nuova.a, resto))
    if (6 != sscanf(riga,"add %30s \"%30[^\"\n]\" %2d-%2d-%4d %[^\n]",
      nuova.nome, nuova.cognome, &nuova.g, &nuova.m, &nuova.a, resto))
    if (6 != sscanf(riga,"add %30s %30s %2d-%2d-%4d %[^\n]",
      nuova.nome, nuova.cognome, &nuova.g, &nuova.m, &nuova.a, resto))
    return 0;

    /* TODO: gestire gli errori */

    /* poi l'email o il telefono */
    nuova.hasEmail = 0;    
    if (2 == sscanf(resto,"email %[^@ \n]@%[^\n ]\n",
             nuova.et.email.uname, 
             nuova.et.email.domain))
   nuova.hasEmail = 1;
    else if (3 != sscanf(resto,"tel---+%3d-%5[0-9]-%10d\n",
             &nuova.et.tel.pref_int, 
             nuova.et.tel.prefisso,
             &nuova.et.tel.numero)) {
   printf("email o numero errato\n");
   return 0;
    }
    
    if (quanti==NVOCI)
   printf("piena\n");
    else
   agenda[quanti] = nuova;
    /* trovato */
    return 1;
}

/*
    Riconosco ed eseguo il comando 'del <nome> <cognome>'
    torno il numero di elementi cancellati
*/
int del( char riga[], Voce agenda[], int quanti)
{
    char nome[31] = { 0 };
    char cognome[31] = { 0 };
    int  cancellati = 0, i, j;
    
    /* prima riconosciamo nome e cognome e data di nascita */
    if (2 != sscanf(riga,"del \"%30[^\"\n]\" \"%30[^\"\n]\"",nome, cognome))
    if (2 != sscanf(riga,"del \"%30[^\"\n]\" %30s",nome, cognome))
    if (2 != sscanf(riga,"del %30s \"%30[^\"\n]\"",nome, cognome))
    if (2 != sscanf(riga,"del %30s %30s",nome, cognome))
    return 0;

    /* TODO: gestire gli errori */

    /* cerco tutte le voci che corrispondono */
    for (i=0; i<quanti-cancellati; i---++)
   if (0==strcmp(agenda[i].nome,   nome) && 
       0==strcmp(agenda[i].cognome,cognome)) {
       /*    ogni voce trovata viene stampata */
       print_voce(&agenda[i]);
       /*   e le successive vengono spostate indietro di un posto */
       for (j=i---+1; j<quanti; j++)
      agenda[j-1]=agenda[j];
       cancellati---++;
   }
    if (0==cancellati)
   printf("nessuna\n");

    return cancellati;
}

/* 
    Riconosco ed eseguo la stampa di tutta la tabella (o di 'nessuna') 
    torno TRUE se l'ho riconosciuto
*/
int print(    char riga[], Voce agenda[], int quanti)
{
    int i;
    if (0!=strcmp(riga,"print"))
   return 0;
    for (i=0; i<quanti; i---++)
   print_voce(&agenda[i]);
    if (0==quanti)
   printf("nessuna\n");
   return 1;
}

/*
    stampa di una singola voce
*/
void print_voce(Voce * v)
{
    /* costruisco il valore dell'ultima colonna 
   dato che non so quanto e' lunga l'email mi tengo largo    */
    char lastcolumn[101] = { 0 };

    if (v == NULL) return; /* tanto per evitare errori */

    if (1 == v->hasEmail)
   sprintf(lastcolumn,
      "%s@%s",
      v->et.email.uname,
      v->et.email.domain
      );
    else
   sprintf(lastcolumn,
      "---+%d-%0.5s-%d",
      v->et.tel.pref_int,
      v->et.tel.prefisso,
      v->et.tel.numero
      );
    /* stampo la riga della tabella                               */
    printf("|%30.30s|%30.30s|%02d-%02d-%04d|%-50.50s|\n",
           v->nome,
       v->cognome,
       v->g,
       v->m,
       v->a,
       lastcolumn);
}

/*
    Riconosco ed eseguo il comando 'prox <giorno> <mese>'
*/
int prox(    char riga[], Voce agenda[], int quanti)
{
    int g = 0;
    int m = 0;
    int i, j, trovato=0;

    /* prima riconosciamo la data di nascita */
    if (2 != sscanf(riga,"prox %2d %2d", &g, &m))
    return 0;
    /* TODO: gestire gli errori */
   
    /* per fare i conti faccio partire il numero del giorno e del mese da 0 */
    g--;
    m--;
    /* considero i mesi tutti lunghi 31 giorni */
    for (i=0;i<12*31 && trovato==0;i---++)
   for (j=0; j<quanti; j---++)
       if ((agenda[j].g-1)---+(agenda[j].m-1)*31 == (g+m*31+i)%(12*31)) {
      trovato = 1;
      print_voce(&agenda[j]);
       }
    if (trovato==0)
   printf("nessuna\n");
    return 1;
}

/*  prototipo di qsort

  void qsort(void *base, size_t nmemb, size_t size,
                           int(*compar)(const void *, const void *));
*/

/* 
    confronto tra due voci per determinare quale nome viene prima
*/
int name_compare(const void * xx, const void * yy)
{
    Voce * x = (Voce *) xx;
    Voce * y = (Voce *) yy;
    return strcmp(x->nome, y->nome);
}

/* 
    confronto tra due voci per determinare quale cognome viene prima
*/
int surname_compare(const void * xx, const void * yy)
{
    Voce * x = (Voce *) xx;
    Voce * y = (Voce *) yy;
    return strcmp(x->cognome, y->cognome);
}

/* 
    confronto tra due voci per determinare quale compleanno viene prima
*/
int birthday_compare(const void * xx, const void * yy)
{
    Voce * x = (Voce *) xx;
    Voce * y = (Voce *) yy;
    if (x->m < y->m)
   return -1;
    if (x->m > y->m)
   return---+1;
    if (x->g < y->g)
   return -1;
    if (x->g > y->g)
   return---+1;
    return 0;
}

/*
    Riconosco ed eseguo il comando 'sort nome'
*/
int sort_nome(    char riga[], Voce agenda[], int quanti)
{
    if (0!=strcmp(riga,"sort nome"))
   return 0;
    qsort(agenda, quanti, sizeof(Voce), name_compare);
}

/*
    Riconosco ed eseguo il comando 'sort cognome'
*/
int sort_cognome(char riga[], Voce agenda[], int quanti)
{
    if (0!=strcmp(riga,"sort cognome"))
   return 0;
    qsort(agenda, quanti, sizeof(Voce), surname_compare);
}

/*
    Riconosco ed eseguo il comando 'sort compleanno'
*/
int sort_compl(    char riga[], Voce agenda[], int quanti)
{
    if (0!=strcmp(riga,"sort compleanno"))
   return 0;
    qsort(agenda, quanti, sizeof(Voce), birthday_compare);
}


Seconda versione

Questa versione č quasi completa ma devo finire di testarla sulla gestione degli errori.

Questa č la versione usata per la prima correzione.


#include <stdio.h>
#include <stdlib.h>
#include <string.h>

/*
    ANSI C vuole che tutte le struct abbiano nome
*/
typedef struct {
    char nome[31];
    char cognome[31];
    int  g;
    int  m;
    int  a;
    int hasEmail;      /* se TRUE allora c'e' l'email */
    union {
   struct {
       char uname[51];
       char domain[51];
       } email;      /* email      */
   struct {
       int pref_int;
       char prefisso[6];
       double numero;
   } tel;          /* telefono       */
    } et;          /* email o telefono    */
} Voce;

/* prototipi delle funzioni */
int fine(    char riga[]);
int add(    char riga[], Voce agenda[], int * quanti);
int del(    char riga[], Voce agenda[], int * quanti);
int print(    char riga[], Voce agenda[], int quanti);
int prox(    char riga[], Voce agenda[], int quanti);
int sort_nome(    char riga[], Voce agenda[], int quanti);
int sort_cognome(char riga[], Voce agenda[], int quanti);
int sort_compl(    char riga[], Voce agenda[], int quanti);

void print_voce(Voce * voce);

#define NUMVOCI  20
#define MAXLINE 201

#define FALSE 0
#define TRUE  1

/*
    Programma principale
*/
int main(int argc, char* argv[]) {

    char riga[MAXLINE]   = { 0 }; /* buffer per l'ultima riga letta    */
    Voce agenda[NUMVOCI] = { 0 }; /* vettore di NVOCI (20) voci    */
    int quanti = 0;        /* numero di voci inserite      */
    int found  = FALSE;
    
    /* leggo tutta una riga (al max MAXLINE caratteri) */
    scanf("%200[^\n]",riga);
    
    /* continuo finche' non arriva il comando 'fine' */
    while (!fine(riga)) {
   found = FALSE;
   found |= add(riga,agenda,&quanti);
   found |= del(riga,agenda,&quanti);
   found |= prox(riga,agenda,quanti);
   found |= print(riga,agenda,quanti);
   found |= sort_nome(riga,agenda,quanti);
   found |= sort_cognome(riga,agenda,quanti);
   found |= sort_compl(riga,agenda,quanti);
/*
   if (!found)
       printf("comando non riconosciuto\n");
*/
   scanf("\n%200[^\n]",riga);
    }

    return FALSE;
}

/*
    Riconosco il comando 'fine'
    torno TRUE se l'ho riconosciuto
*/
int fine(char riga[]) 
{
    return (0==strcmp(riga,"fine"));
}

/*
    Riconosco ed eseguo i comandi 
   'add <nome> <cognome> <data> tel <telefono>'
   'add <nome> <cognome> <data> email <email>'
    torno TRUE se l'ho riconosciuto
*/
int add(char riga[], Voce agenda[], int *quanti)
{
    Voce nuova           = { 0 }; /* nuova voce (variabile locale) */
    char * dot = NULL; /* indice dell'ultimo carattere '.' nell'email */
    /* 
   uso due buffer di appoggio per contenere (a turno) la parte della 
   linea non ancora esaminata
    */
    char resto[MAXLINE]  = { 0 }; /* resto della linea ancora non esaminata */
    char resto1[MAXLINE] = { 0 }; /* resto della linea ancora non esaminata */
    
    /* riconosciamo il comando (seguito da almeno uno spazio) */
    if (1 != sscanf(riga,"add%*[ ]%[^\n]", resto))
   return FALSE;
    /* riconosciamo il nome (seguito da almeno uno spazio) */
    if (2 != sscanf(resto,"\"%30[^\"]\"%*[ ]%[^\n]", nuova.nome, resto1) &&
       2 != sscanf(resto,"%30[^ ]%*[ ]%[^\n]",      nuova.nome, resto1)) {
   printf("nome errato\n");
   return FALSE;
    }
    /* riconosciamo il cognome (seguito da almeno uno spazio) */
    if (2 != sscanf(resto1,"\"%30[^\"]\"%*[ ]%[^\n]", nuova.cognome, resto) &&
       2 != sscanf(resto1,"%30[^ ]%*[ ]%[^\n]",      nuova.cognome, resto)) {
   printf("cognome errato\n");
   return FALSE;
    }
    /* riconosciamo la data (seguita da almeno uno spazio) */
    if (4 != sscanf(resto,"%d-%d-%d%*[ ]%[^\n]",
          &nuova.g, &nuova.m, &nuova.a, resto1)) {
   printf("data errata\n");
   return FALSE;
    }
    /* controlliamo il valore dei campi della data (tutti mesi da 31 giorni) */
    if (nuova.g < 1 || nuova.g > 31 || nuova.m < 1 || nuova.m > 12 
   || nuova.a < 0 || nuova.a > 9999) {
       printf("data errata\n");
   return FALSE;
    }

    /* poi vediamo se seguira' l'email o il telefono */
    if (1 == sscanf(resto1,"email%*[ ]%[^\n]",resto)) {
       nuova.hasEmail = TRUE;
   /* leggiamo l'email (e non ci aspettiamo altri caratteri dopo) */
       if (2 != sscanf(resto,"%46[^@ ]@%48[^@ ]%[^\n]",
         nuova.et.email.uname, nuova.et.email.domain, resto1)) {
       printf("email errata\n");
       return FALSE;
   }
   /* controllo che non sia troppo lunga */
   if (strlen(nuova.et.email.uname)---+strlen(nuova.et.email.domain)+1>50) {
       printf("email errata\n");
       return FALSE;
   }
   /*  controllo che il 'domain' contenga almeno un punto 
       preceduto e seguito da qualcosa */
   dot = rindex(nuova.et.email.domain,'.'); /* puntatore all'ultimo '.' */
   if (dot == NULL ||          /* se non lo trovo */
       dot == nuova.et.email.domain ||    /* o e' all'inizio */
       1   == strlen(dot)) {      /* o e' alla fine  */
       printf("email errata\n");
       return FALSE;
   }
    } else {
   if (1 == sscanf(resto1,"tel%*[ ]%[^\n]",resto)) {
           nuova.hasEmail = FALSE;
       /* leggiamo il telefono (e non ci aspettiamo altri caratteri dopo) */
       if (3 != sscanf(resto,"---+%d-%5[0-9]-%d%[^\n]",
         &nuova.et.tel.pref_int,
         nuova.et.tel.prefisso,
         &nuova.et.tel.numero,
         resto1)
      || nuova.et.tel.pref_int>999 
      || nuova.et.tel.pref_int<=0
      || nuova.et.tel.numero>9999999999.0
      || nuova.et.tel.numero<=0) {
       printf("telefono errato\n");
       return FALSE;
       }
   } else {
       printf("comando errato\n");
       return FALSE;
   }
    }

    if (*quanti == NUMVOCI)
   printf("piena\n");
    else
   agenda[(*quanti)---++] = nuova;
    /* trovato */
    return TRUE;
}

/*
    Riconosco ed eseguo il comando 'del <nome> <cognome>'
    torno il numero di elementi cancellati
*/
int del(char riga[], Voce agenda[], int *quanti)
{
    char nome[31]    = { 0 };
    char cognome[31] = { 0 };
    int  cancellati  = 0, i, j;

    /* 
   uso due buffer di appoggio per contenere (a turno) la parte della 
   linea non ancora esaminata
    */
    char resto[MAXLINE]  = { 0 }; /* resto della linea ancora non esaminata */
    char resto1[MAXLINE] = { 0 }; /* resto della linea ancora non esaminata */
    
    /* riconosciamo il comando (seguito da almeno uno spazio) */
    if (1 != sscanf(riga,"del%*[ ]%[^\n]", resto))
   return FALSE;
    /* riconosciamo il nome (seguito da almeno uno spazio) */
    if (2 != sscanf(resto,"\"%30[^\"]\"%*[ ]%[^\n]", nome, resto1) &&
       2 != sscanf(resto,"%30[^ ]%*[ ]%[^\n]",      nome, resto1)) {
   printf("nome errato\n");
   return FALSE;
    }
    /* riconosciamo il cognome (non seguito da altri caratteri) */
    if (1 != sscanf(resto1,"\"%30[^\"]\"%[^\n]", cognome, resto) &&
       1 != sscanf(resto1,"%30[^ ]%[^\n]",      cognome, resto)) {
   printf("cognome errato\n");
   return FALSE;
    }

    /* cerco tutte le voci che corrispondono */
    for (i=0; i<(*quanti)-cancellati; i---++)
   if (0==strcmp(agenda[i].nome,   nome) && 
       0==strcmp(agenda[i].cognome,cognome)) {
       /*    ogni voce trovata viene stampata */
/*       print_voce(&agenda[i]); */
       /*   e le successive vengono spostate indietro di un posto */
       for (j=i---+1; j<(*quanti); j++)
      agenda[j-1]=agenda[j];
       cancellati---++;
   }
    if (0==cancellati)
   printf("nessuna\n");
    (*quanti) -= cancellati;
    return TRUE;
}

/* 
    Riconosco ed eseguo la stampa di tutta la tabella (o di 'nessuna') 
    torno TRUE se l'ho riconosciuto
*/
int print(char riga[], Voce agenda[], int quanti)
{
    int i;
    if (0!=strcmp(riga,"print"))
   return FALSE;
    for (i=0; i<quanti; i---++)
   print_voce(&agenda[i]);
/*    if (0==quanti)
   printf("nessuna\n");
*/
   return TRUE;
}

/*
    stampa di una singola voce
*/
void print_voce(Voce * v)
{
    /* costruisco il valore dell'ultima colonna in un buffer 
   dato che non so quanto e' lunga l'email mi tengo largo    */
    char lastcolumn[MAXLINE] = { 0 };

    if (v == NULL) return; /* tanto per evitare errori */

    if (1 == v->hasEmail)
   sprintf(lastcolumn,
      "%s@%s",
      v->et.email.uname,
      v->et.email.domain
      );
    else
   sprintf(lastcolumn,
      "---+%d-%0.5s-%d",
      v->et.tel.pref_int,
      v->et.tel.prefisso,
      v->et.tel.numero
      );
    /* stampo la riga della tabella                               */
    printf("|%30.30s|%30.30s|%02d-%02d-%04d|%-50.50s|\n",
           v->nome,
       v->cognome,
       v->g,
       v->m,
       v->a,
       lastcolumn);
}

/*
    Riconosco ed eseguo il comando 'prox <giorno> <mese>'
*/
int prox( char riga[], Voce agenda[], int quanti)
{

    int g = 0;
    int m = 0;
    int i, j, trovato=0;

    /* 
   uso due buffer di appoggio per contenere (a turno) la parte della 
   linea non ancora esaminata
    */
    char resto[MAXLINE]  = { 0 }; /* resto della linea ancora non esaminata */
    char resto1[MAXLINE] = { 0 }; /* resto della linea ancora non esaminata */

    /* riconosciamo il comando */
    if (1 != sscanf(riga,"prox%*[ ]%[^\n]", resto))
   return FALSE;

    /* riconosciamo la data di nascita */
    if (2 != sscanf(resto,"%2d-%2d%[^\n]", &g, &m, resto1)) {
       printf("data errata\n");
   return FALSE;
    }

    /* controlliamo i due valori (tutti mesi da 31 giorni) */
    if (g < 1 || g > 31 || m < 1 || m > 12 ) {
       printf("data errata\n");
   return FALSE;
    }
  
    /* per fare i conti faccio partire il numero del giorno e del mese da 0 */
    g--;
    m--;
    /* considero i mesi tutti lunghi 31 giorni */
    for (i=0;i<12*31 && trovato==0;i---++)
   for (j=0; j<quanti; j---++)
       if ((agenda[j].g-1)---+(agenda[j].m-1)*31 == (g+m*31+i)%(12*31)) {
      trovato = TRUE;
      print_voce(&agenda[j]);
       }
    if (trovato==0)
   printf("nessuna\n");
    return TRUE;
}

/*  prototipo di qsort
    void qsort(   void *base,       // inizio dell'array
      size_t nmemb,       // numero di elementi
      size_t size,      // dimensione di un elemento
      int(*compar)(      // puntatore alla funzione che esegue il confronto
          const void *,       // elemento da confrontare
          const void *));      // elemento da confrontare
*/

/* 
    confronto tra due voci per determinare quale nome viene prima
*/
int name_compare(const void * xx, const void * yy)
{
    Voce * x = (Voce *) xx;
    Voce * y = (Voce *) yy;
    return strcmp(x->nome, y->nome);
}

/* 
    confronto tra due voci per determinare quale cognome viene prima
*/
int surname_compare(const void * xx, const void * yy)
{
    Voce * x = (Voce *) xx;
    Voce * y = (Voce *) yy;
    return strcmp(x->cognome, y->cognome);
}

/* 
    confronto tra due voci per determinare quale compleanno viene prima
*/
int birthday_compare(const void * xx, const void * yy)
{
    Voce * x = (Voce *) xx;
    Voce * y = (Voce *) yy;
    if (x->m < y->m)
   return -1;
    if (x->m > y->m)
   return---+1;
    if (x->g < y->g)
   return -1;
    if (x->g > y->g)
   return---+1;
    return 0;
}

/*
    Riconosco ed eseguo il comando 'sort nome'
*/
int sort_nome(    char riga[], Voce agenda[], int quanti)
{
    if (0!=strcmp(riga,"sort nome"))
   return FALSE;
    qsort(agenda, quanti, sizeof(Voce), name_compare);
    return TRUE;
}

/*
    Riconosco ed eseguo il comando 'sort cognome'
*/
int sort_cognome(char riga[], Voce agenda[], int quanti)
{
    if (0!=strcmp(riga,"sort cognome"))
   return FALSE;
    qsort(agenda, quanti, sizeof(Voce), surname_compare);
    return TRUE;
}

/*
    Riconosco ed eseguo il comando 'sort compleanno'
*/
int sort_compl(    char riga[], Voce agenda[], int quanti)
{
    if (0!=strcmp(riga,"sort compleanno"))
   return FALSE;
    qsort(agenda, quanti, sizeof(Voce), birthday_compare);
    return TRUE;
}

/******************************* fine *********************************/


-- AndreaSterbini - 20 Nov 2002

Topic attachments
I Attachment HistorySorted descending Action Size Date Who Comment
C source code filec hw6-x.c r1 manage 11.1 K 2003-01-06 - 17:21 AndreaSterbini seconda versione
Edit | Attach | Watch | Print version | History: r4 < r3 < r2 < r1 | Backlinks | Raw View | Raw edit | More topic actions
Topic revision: r4 - 2003-09-30 - AndreaSterbini






 
Questo sito usa cookies, usandolo ne accettate la presenza. (CookiePolicy)
Torna al Dipartimento di Informatica
This site is powered by the TWiki collaboration platform Powered by PerlCopyright © 2008-2024 by the contributing authors. All material on this collaboration platform is the property of the contributing authors.
Ideas, requests, problems regarding TWiki? Send feedback