| |||
| © Perl Mongers Italia. Tutti i diritti riservati. | |||
Non riscrivere i PDF - riusali!Di recente mi sono lasciato sedurre dal libro Getting things done: The Art of Stress-Free Productivity di David Allen. Di che si tratta? Di un sistema di accorgimenti utili per non dimenticare di fare le cose e ridurre lo stress creato dalle stesse, che tendono a perseguitarti finché non le hai completate. La soluzione, senza temere di rovinare niente del libro, consiste semplicemente nello scriversele. Geniale, no? Nella mia strada verso la redenzione dalla smemoratezza ho avuto anche la fortuna di incontrare qualche buon samaritano. Un ottimo esempio è costituito dal sito dedicato al D*I*Y Planner (http://www.diyplanner.com/), una vera e propria agenda/planner che potete stamparvi da soli, includendo i moduli che volete. Non sto qui a tediarvi sul come e sul cosa, ma io sono rimasto particolarmente colpito dalla versione hipster PDA del planner suddetto, che viene fornito in varie forme, la più interessante è quella in PDF con quattro pagine per foglio (trovate il tutto qui: http://www.diyplanner.com/templates/official/hpda). Questo bel documentino PDF ha 29 facciate, ognuna fondamentalmente dedicata ad un differente aspetto dell'organizzazione; ognuno può sceglierne le parti che ritiene più utili, tralasciando le altre. A questo punto sono sorti i miei problemi:
E qui, finalmente, entra in ballo Perl insieme al suo modulo PDF::Reuse. Un po' di contestoIl modulo in questione lo potete trovare su CPAN, consiglio una ricerca tipo PDF::Reuse. La descrizione è particolarmente illuminante, mi permetto di tradurla:
Magari non abbiamo bisogno di andare tanto veloci, però l'idea del riutilizzo è stuzzicante! Getting (this) thing done!Andiamo ad utilizzare il modulo per i nostri biechi scopi, dunque. Il nostro obiettivo è scrivere uno script che ci consenta di selezionare le pagine desiderate dal file di partenza, in un ordine dato da noi e con ripetizioni (per poter stampare fronte-retro senza troppo lavoro di stampante). Tanto per perdere un po' di tempo, faremo in modo da implementare la seguente sintassi sulla riga di comando:
Mi sembra abbastanza, non indugiamo oltre: 1 #!/usr/bin/perl
2 use strict;
3 use warnings;
4 use PDF::Reuse;
5 my $ifile = shift;
6 my $reps = 1;
7 # Apro il documento PDF di uscita - viene inviato su STDOUT
8 # perche' non diamo alcun argomento a prFile()
9 prFile();
10 # Itero sui vari comandi in ingresso, che possono essere stati
11 # separati in chiamata (suddivisi dunque in @ARGV), oppure possono
12 # essere intervallati da spazi o virgole (da cui la map/split).
13 foreach my $cmd (map { split /[ ,]/, $_ } @ARGV) {
14 # Per default, il comando e` semplicemente un numero di pagina,
15 # per cui $start e $end sono inizializzate con essa. $itreps
16 # memorizza il numero di ripetizioni per questa particolare
17 # iterazione, e per default e` uguale all'impostazione globale
18 my ($start, $end, $itreps) = ($cmd, $cmd, $reps);
19 # Analizza se il comando non specifica semplicemente un numero
20 # di pagina
21 if ($cmd =~ /^x(\d+)$/) { # xNN
22 $reps = $1; # Imposta ripetizioni
23 next; # e prosegui con il prossimo comando
24 }
25 elsif ($cmd =~ /^(\d+)x(\d+)$/) { # PPxNN
26 $start = $1; # Inizio e fine coincidono con $1,
27 $end = $1; # la prima parte di PPxNN
28 $itreps *= $2; # Le ripetizioni per questa iterazione
29 }
30 elsif ($cmd =~ /^(\d+)-(\d+)$/) { # PI-PF
31 $start = $1;
32 $end = $2;
33 }
34 # Posso determinare quali sono le pagine da inserire in questa
35 # iterazione. Ciascuna pagina nell'intervallo $start .. $end
36 # viene ripetuta per un numero $itreps di volte
37 my @pages = map { ($_) x $itreps } $start .. $end;
38 # Itero sulle pagine determinate e le estraggo dal documento
39 # di input. Per ciascuna di esse stampo anche un feedback su
40 # STDERR
41 foreach my $page (@pages) {
42 prDoc($ifile, $page, $page);
43 print STDERR "$page ";
44 }
45 } ## end foreach my $cmd (map { split...
46 print STDERR "\n";
47 # Chiudo il documento PDF di uscita
48 prEnd();
Il primo blocco (righe 1-4) è roba già vista, vero? Si imposta l'interprete di default per Unix, si utilizzano Il primo argomento a linea di comando deve essere il nome del file PDF di partenza, per cui lo mettiamo in La generazione di un file PDF prevede l'apertura e la chiusura del file stesso, che può essere mandato su STDOUT. L'apertura viene effettuata utilizzando la funzione La riga 13 suddivide i vari comandi impostati. Poiché abbiamo detto che possiamo separare i campi con spazi e virgole, filtriamo Per generalità, lavoreremo sempre come se stessimo trattando un intervallo di pagine, anche se la prima e l'ultima possono coincidere. In queste ipotesi, per default assumiamo che il comando sia un semplice numero di pagina, ed impostiamo Successivamente, andiamo a verificare se, per caso, l'utente ha specificato in realtà altri tipi di comandi:
A questo punto sono pronto per generare pagine, ma faccio l'operazione in due tempi per maggiore leggibilità.. L'intervallo Successivamente si itera (riga 41) su tale intervallo, per poter estrarre la pagina dal file iniziale con la funzione Prendi e manda!Mi è capitato di dover automatizzare dei processi di questo tipo:
Si, esatto: era qualcosa che mi è stato richiesto per due giorni di seguito, il che ha fatto scattare il principio della lazyness: lavorare subito (di programmazione) per non lavorare più (fa tutto lo script). Il piano d'azione è il seguente:
Il tutto senza dover passare per file temporanei e simili! Vediamo come... 1 #!/usr/bin/perl
2 use strict;
3 use warnings;
4 use LWP::Simple;
5 use Mail::Sender;
6 use Archive::Zip;
7 # Un po' di configurazione
8 my $resource = "http://www.example.com/resource.pdf";
9 my $smtp_server = '10.20.30.40';
10 # Prendi il documento
11 print "Prendo $resource\n";
12 my $content = LWP::Simple::get($resource)
13 or die "niente da scaricare, riprovare piu` tardi";
14 # Impacchetta
15 print "Impacchetto\n";
16 my $zip = Archive::Zip->new();
17 $zip->addString($content, "risorsa.pdf");
18 my $zipped;
19 {
20 open my $fh, ">", \$zipped;
21 die "impossibile impacchettare!"
22 unless $zip->writeToFileHandle($fh) == Archive::Zip::AZ_OK;
23 }
24 # Spedisci via email
25 print "Spedisco\n";
26 my $sender = Mail::Sender->new(
27 {
28 from => '"Pinco Pallino- Automatic" <pinco@example.com>',
29 to => '"Tizio Caio" <tizio@example.com>',
30 subject => "Risorsa trovata!",
31 smtp => $smtp_server,
32 }
33 )
34 or die "niente da fare!";
35 # Vogliamo avere un messaggio ed un allegato sul quale vogliamo
36 # mantere pieno controllo, per cui utilizziamo OpenMultipart(),
37 # Body() e Part() per costruire il messaggio, e Close() per
38 # concluderlo.
39 $sender->OpenMultipart()
40 or die "impossibile chiamare OpenMultipart()";
41 $sender->Body({msg => "Beh, che si dice?\n"});
42 $sender->Part(
43 {
44 description => "Rassegna stampa del $date",
45 ctype => 'application/x-zip-encoded',
46 encoding => 'Base64',
47 disposition =>
48 qq{attachment; filename="$date.zip"; type="ZIP Archive"},
49 msg => $zipped,
50 }
51 );
52 $sender->Close() or die "impossibile chiamare Close()";
53 # Finito!
54 print "Fatto\n";
Il primo blocco (righe 1-6) dichiara le nostre intenzioni: fare le cose pulite (strict e warnings), ed utilizzare tanti bei moduli disponibili. Dopo una prima fase di configurazione (righe 7-9), in particolare riguardante la risorsa da prendere e il server SMTP da utilizzare per inviare l'e-mail, si entra nel vivo dello script. La parte prendi si risolve in poche righe (dalla 10 alla 13): si utilizza LWP::Simple, che mette a disposizione un'interfaccia minimale ma efficace per scaricare una risorsa su web. Se il download non va a buon fine (nel qual caso la funzione LWP::Simple::get restituisce undef) si esce subito dallo script, con un messaggio di errore. Altrimenti... si prosegue. La parte impacchetta si avvale dei servigi di Archive::Zip. Tale modulo è in grado di lavorare direttamente sui dati in memoria, senza bisogno dunque di generare file temporanei. In questo caso abbiamo a che fare con un'interfaccia orientata agli oggetti, per cui ci facciamo generare un oggetto (riga 16) e chiamiamo il metodo di aggiunta di un elemento in memoria all'archivio Zip in costruzione mediante il metodo addString (riga 17), impostando anche il nome che tale sequenza deve avere come "file" nell'archivio ("risorsa.pdf" nel nostro caso). Il blocco di codice successivo (righe 18-23) realizza un piccolo inganno per Archive::Zip, che è abitutato a scrivere su file gli archivi compressi. Si apre dunque un file con open (riga 20), ma invece di un vero file nel filesystem si apre, in realtà,, una variabile scalare utilizzata come destinazione, ossia $zipped. Tale possibilità si ha solamente dalla versione 5.8 di Perl, quindi attenzione! Il resto della "scrittura su file" procede come di consueto. Anche qui, se qualcosa va storto si esce immediatamente con un messaggio di errore. A questo punto la variabile $zipped contiene il file compresso, come se l'avessimo appena letto da un file vero e proprio. Siamo pronti alla spedizione, che si avvale - l'avreste mai detto? - di un'interfaccia ad oggetti. Questa volta il costruttore richiede un po' più di parametri (righe 26-34); niente di trascendentale come si può vedere, con la solita interruzione se qualcosa va male. Per spedire un'e-mail con un messaggio ed un allegato potremmo utilizzare il metodo MailFile, che ci consente di fare tutto in un colpo solo; in questo caso, però, vogliamo avere un controllo più stretto sull'header relativo al file da spedire, per cui scegliamo la strada più lunga costruendo il messaggio pezzo per pezzo. Utilizziamo allora OpenMultipart (riga 39), che imposta appunto il messaggio come composto da più parti. Il corpo del messaggio è impostato alla riga successiva con il metodo Body (ok, in questo caso ce lo potevamo evitare, visto il messaggio!), mentre l'allegato viene impostato con il metodo Part (righe 42-51), che può essere chiamato anche più volte per aggiungere ulteriori allegati. Anche in questo caso i parametri passati sono abbastanza ovvi. La chiusura del messaggio con il metodo Close (riga 52), infine, ha anche l'effetto di far partire l'invio vero e proprio dell'e-mail. Il che termina anche la lista di quello che dovevamo fare! Che fanno al cinema?Premessa: l'esempio dato si avvale di informazioni liberamente disponibili su Internet. L'autore dell'esempio non ha trovato nessun tipo di indicazione avversa al trattamento automatico dei dati disponibili sul sito Virgilio.it, nemmeno un semplice robots.txt. Se vi piace il cinema, sicuramente utilizzerete qualche servizio on-line per vedere dove, e quando, fanno il film che vi interessa tanto vedere. Personalmente utilizzo il servizio messo a disposizione da Virgilio.it (http://www.virgilio.it/), che consente di restringere la ricerca alla propria città, e di ordinare i risultati sia per sala che per titolo. Come al solito, però, a me non basta; prima di tutto perché mi piace lavorare a riga di comando (ad esempio per utilizzare l'onnipresente grep), secondo poi perché a volte ho voglia di restringere la ricerca a determinate fasce orarie. Cosa meglio di uno script Perl, allora? I film per le sale di Roma sono reperibili all'indirizzo http://film.spettacolo.virgilio.it/cinema/insala.php/citta=10661. Diamo un'occhiata alla parte interessante di una pagina di esempio: <</span><span style="font-weight: bold; font-size: 10pt; font-family:
'Courier New'">table</span><span style="font-size: 10pt; font-family:
'Courier New'"> cellpadding=5 cellspacing=1 border=0 width=100%
class=listaElenco>
<tr>
<td width=15%><a href="/cinema/insala.php?citta=10661">Roma</a></td>
<td width=30%><a href=#
onclick="PopUpWin('popsala.php?id=469','Sale','scrollbars=yes,width=300,height=350')">Alhambra</a>
- Sala 2<br>
Via Pier delle Vigne, 4 <br>
<i>Tel.</i> 0666012154<br>
<i>Orari:</i> 16:00 18:15 20:15 22:40</td>
<td width=55%><a
href="http://film.spettacolo.virgilio.it/cinema/scheda.php?film=30689">Bambole
russe</a> (Francia/Gran Bretagna, 2005)<br>
<i>con</i> R. Duris, C. De France, A. Tautou<br>
<i>genere</i> commedia-sentimentale</td>
</tr>
Quello che segue si avvale di tutta la potenza di LWP::Simple (per scaricare la pagina degli spettacoli), e di HTML::TableExtract per analizzare il file scaricato senza doversi destreggiare troppo nell'infida terra del parsing HTML. 1 #!/usr/bin/perl
2 use strict;
3 use warnings;
4 use HTML::TableExtract;
5 use LWP::Simple;
6 # L'URL di base per Virgilio Cinema
7 my $baseurl = 'http://film.spettacolo.virgilio.it/cinema/insala.php';
8 # La citta` di roma e` all'indice 10661
9 my $url = "$baseurl?citta=10661";
10 print STDERR "scarico da [$url]... ";
11 my $html = get($url) or die "\ncould not get the page";
12 print STDERR "fatto\n";
13 # Effettua il parsing del file, isolando le tabelle con classe
14 # "listaElenco" - ce ne dovrebbe essere una sola
15 my $te = HTML::TableExtract->new(
16 attribs => {class => 'listaElenco'},
17 keep_html => 1
18 );
19 $te->parse($html);
20 # Estrai i dati dalle tabelle trovate
21 my %data;
22 foreach my $table ($te->tables) {
23 print STDERR "Trovata tabella\n";
24 foreach my $row ($table->rows) {
25 my $title = film_title($row->[2]);
26 my $place = film_place($row->[1]);
27 # $data{$title}{$place} contiene/deve contenere un riferimento
28 # ad un array contenente gli orari degli spettacoli
29 push @{$data{$title}{$place}},
30 split(/\s+/, film_schedule($row->[1]));
31 } ## end foreach my $row ($table->rows)
32 } ## end foreach my $table ($te->tables)
33 # Stampe finali, ordinate per titolo, sala e orario
34 foreach my $title (sort keys %data) {
35 foreach my $place (sort keys %{$data{$title}}) {
36 my @schedules = sort @{$data{$title}{$place}};
37 print "[$title] [$place] [@schedules]\n";
38 }
39 } ## end foreach my $title (sort keys...
40 # Estrazioni di titolo, sala ed orari
41 sub film_title {
42 return ($_[0] =~ /<(?:a|b).*?>(.*?)<\/(?:a|b)>/i ? $1 : '');
43 }
44 sub film_place { return ($_[0] =~ /<a.*?>(.*?)<\/a>/i ? $1 : '') }
45 sub film_schedule { return ($_[0] =~ /\s+([\d :]+)$/i ? $1 : '') }
L'inizio è il solito (righe 1-5): utilizziamo strict e warnings perché siamo coscienziosi, ed includiamo anche i due moduli che ci permetteranno di scrivere lo script senza troppe ansie. Alla riga 7 viene impostata l'URL di base del servizio di Virgilio, che viene poi personalizzata alla riga 9, dove viene anche impostata la città da analizzare. Il download viene effettuato mediante il metodo get (riga 11), importato automaticamente da LWP::Simple; questo restituisce undef se qualche cosa va storto, nel qual caso lo script terminerà con un messaggio di errore. A questo punto la pagina scaricata si trova nella variabile $html. Il modulo HTML::TableExtract mette a disposizione un'interfaccia ad oggetti, per cui prima di tutto dobbiamo procurarcene uno (riga 15), per poi seguire i passi necessari all'analisi della tabella che ci interessa. Si noti che, nel costruttore dell'oggetto, vengono impostate le condizioni di filtraggio: avendo notato che la tabella che ci interessa contiene l'attributo di classe 'listaElenco', indichiamo tale restrizione in modo da eliminare le altre tabelle (possibilmente) presenti nella pagina. Dopo aver effettuato il parsing della pagina scaricata (riga 19), iteriamo sulle varie tabelle individuate (riga 22). Questa iterazione è in realtà non necessaria, visto che la tabella dovrebbe essere unica, ma è meglio andare sul sicuro. Nell'iterazione, la variabile $table itera sulla lista delle tabelle restituita dal metodo tables: nel nostro caso, come detto, tale lista conterrà solo un elemento. La tabella $table viene analizzata riga per riga (linea 24); il metodo rows consente infatti di estrarre una lista di tutte le righe disponibili. Il titolo e la sala vengono identificati dai relativi campi nella riga della tabella (righe 25 e 26), utilizzando delle funzioni ad-hoc implementate in fondo allo script (righe 41-44). Queste due informazioni consentono di accedere ad una lista di orari contenuta nella struttura multilivello rappresentata dall'hash %data. La prima chiave è costituita dal titolo, la seconda dalla sala e l'elemento puntato è un riferimento anonimo ad un array di orari, che viene riempito con gli orari desunti dalla riga sotto analisi (righe 29-30). Quando l'analisi della pagina è terminata, la struttura %data contiene tutto quello che ci serve per sbizzarrirci: inserire i dati in un database, mettere a disposizione un'interfaccia di ricerca... Nel nostro caso siamo molto più prosaici: facciamo una semplice stampa dei dati. Poiché la struttura è multilivello, annidiamo un ciclo per ciascuno di essi (tranquilli, sono solo due): al livello esterno troviamo i titoli (riga 34), che iteriamo in forma ordinata, mentre al livello interno troviamo la sala (riga 35), anche qui iterata in ordine alfabetico. Per comodità, estraiamo l'array degli orari (riga 36) e procediamo con la stampa (riga 37). Si noti che la stampa di un array fra virgolette doppie fa sì che gli elementi siano separati fra loro dalla stringa contenuta nella variabile speciale $", che per default corisponde ad uno spazio. Una cosa in meno a cui pensare! Vietato ai minoriC'è un modulo su CPAN che andrebbe vietato ai minori. Prima che vi prendiate a gomitate per raggiungere il browser più vicino e dare fondo alla vostra fantasia per cercare roba tipo Free::Nude su CPAN fatemi andare avanti, potreste perdere il vostro tempo! Ricorderete che a scuola, specialmente alle elementari ed alle medie, la parola calcolatrice era stata bandita dal vocabolario di un qualsiasi insegnante di matematica. La ragione è semplice: se devi imparare a fare i conti, ossia se devi imparare come si fanno i conti e soprattutto perché si fanno in quel modo, avere la pappa pronta in una macchinetta che sa sempre la risposta giusta non aiuta molto. Quando hai capito, invece, puoi benissimo permetterti di dimenticare tutto ed usarla! Con la scrittura credo debba essere uguale: dare delle scorciatoie prima che certi concetti siano ben radicati può essere molto, molto pericoloso. E qui veniamo al modulo proibito, quello che mette ordine nel testo al posto nostro: Text::Beautify. I minori sono gentilmente pregati di tornare alle grammatiche ed alle antologie, grazie. Text::Beautify consente di eliminare quelle noiosissime violazioni delle regole di buona scrittura che rendono alcuni testi tanto noiosi da leggere. Se aprite un qualsiasi libro stampato (o quasi), potete infatti notare che vengono rispettate queste regole semplicissime:
Sono regole semplici, ma pur sempre regole - che vengono regolarmente infrante in moltissimi testi scritti "di fretta", come ad esempio nelle e-mail o nei commenti che vengono lasciati in giro su Internet. Text::Beautify viene allora incontro alla pigrizia imperante: dategli un testo e tirerà fuori una versione più umana e gradevole da leggere. Vediamo un esempio: 1 #!/usr/bin/perl
2 use strict;
3 use warnings;
4 use Text::Beautify qw( beautify );
5 my $text = " qualcosa che non va bene ,direi ! ";
6 print "Originale: '$text'\n";
7 $text = beautify($text);
8 print "Trasformato: '$text'\n";
Lo script di esempio è di una banalità sconvolgente. Da notare che, in fase di utilizzo del modulo (riga 4), viene specificato il parametro 7bis $text = Text::Beautify::beautify($text); Niente di sconvolgente, ma in uno script così semplice possiamo permetterci di importare la funzione senza temere di incappare in collisioni. L'uscita è semplicemente: Originale: ' qualcosa che non va bene ,direi ! ' Trasformato: 'Qualcosa che non va bene, direi!' Comodo vero? Ma che non sia una scusa per scrivere male! E se...... volessi mettere a posto la punteggiatura, ma lasciare le lettere di inizio periodo così come sono? O tenere le punteggiature multiple? Non sono richieste così campate in aria, mi rendo conto; fortunatamente se n'è reso conto anche cog, che ha messo a disposizione la possibilità di disabilitare le caratteristiche che non interessano. Una rapida occhiata al manuale (che, ricordo, è accessibile con il comando 1 #!/usr/bin/perl
2 use strict;
3 use warnings;
4 use Text::Beautify qw( beautify disable_feature );
5 my $brutto = " cog ,sapete,, va scritto sempre minuscolo ! ";
6 print "Brutto: '$brutto'\n";
7 my $errato = beautify($brutto);
8 print "Errato: '$errato'\n";
9 disable_feature('uppercase_first');
10 my $bello = beautify($brutto);
11 print "Bello : '$bello'\n";
La riga 4 non ci sorprende: poiché abbiamo bisogno di un'altra funzione da quel modulo, indichiamo che vogliamo importarla nel nostro spazio dei nomi. Alla riga 7 utilizziamo Brutto: ' cog ,sapete,, va scritto sempre minuscolo ! ' Errato: 'Cog, sapete, va scritto sempre minuscolo!' Bello : 'cog, sapete, va scritto sempre minuscolo!' Attenzione agli oggettiIl modulo mette a disposizione un'interfaccia orientata agli oggetti: mi è sembrata una cosa piuttosto golosa vista la possibilità di abilitare o disabilitare determinate caratteristiche di trasformazione del testo. Purtroppo, però, nella versione 0.08 (quella disponibile al momento) il supporto per l'interfaccia ad oggetti sembra limitarsi alla funzione Upload, che passione!Mi è capitato di recente di dover spedire dei file molto grandi ad un paio di colleghi; con molto grandi intendo maggiori di una ventina di megabyte, che è già abbastanza al di là della dimensione massima di un allegato che non mi infastidisce ricevere. Visto che ho un server a disposizione, mi è sembrato naturale mettere su un piccolo "servizio" personale per l'upload dei file. Ecco cosa è venuto fuori: 1 #!/usr/bin/perl -T
2 use strict;
3 use warnings;
4 use CGI;
5 use File::Basename qw( basename );
6 use Readonly;
7 # Configuration
8 Readonly my $fieldname => 'uploaded_file';
9 Readonly my $base_directory => "/percorso/per/upload";
10 Readonly my $base_url => "http://mio.server.it/upload";
11 my $q = CGI->new();
12 my $msg;
13 $msg = gestisci_upload_entrante() if $q->param('uploaded_file');
14 print $q->header(),
15 $q->start_html('Upload semplice semplice');
16 print $q->h1($msg), $q->hr() if $msg;
17 print $q->h1("Upload"),
18 $q->start_multipart_form(),
19 'File: ', $q->filefield(-name => $fieldname), $q->br(),
20 $q->submit(-name => 'Invia'),
21 $q->end_form(),
22 $q->end_html();
23 sub gestisci_upload_entrante {
24 chdir $base_directory or return "chdir(): $!";
25 my $fn = basename($q->param($fieldname))
26 or return "nessun nome di file";
27 $fn =~ s/[^\w\d .-]//g;
28 ($fn) = $fn =~ /([\w\d .-]+)/;
29 return "Errore: nessun nome di file valido" unless $fn;
30 my @alphabeth = ('a' .. 'z');
31 while (-e $fn) {
32 $fn = $alphabeth[rand @alphabeth] . $fn;
33 }
34 my $fh = $q->upload($fieldname)
35 or return "Problemi nell'upload di '$fn'";
36 binmode $fh;
37 open my $out, '>', $fn or return "open(): $!";
38 binmode $out;
39 while (read $fh, my $data, 8192) { print {$out} $data }
40 return "Upload ok: $base_url/$fn";
41 }
L'inizio (righe 1..6) è più o meno il solito: usiamo Avete fatto caso all'opzione Le righe 7..10 riportano alcune configurazioni: in particolare, il campo nel form CGI che servirà per l'upload verrà chiamato Per evitare di spargere queste stringhe in giro per lo script, correndo il rischio di sbagliare qualcosa, ho deciso di utilizzare delle variabili. L'approccio migliore, in questi casi, consiste nell'utilizzare un sistema che forzi l'utilizzo di queste variabili come costanti; usiamo qui il modulo La riga 11 dichiara e definisce La funzione Le righe 14..22 si occupano di generare la risposta da inviare al browser, utilizzando le funzioni standard del modulo Le righe 23..41 contengono l'implementazione della funzione di gestione dell'upload, ossia Le righe 25..33 si occupano di determinare il nome del file da utilizzare per salvare l'upload entrante, con alcuni controlli paranoici. In particolare, attraverso la funzione La riga 28 merita un commento. Abbiamo detto che stiamo operando in modalità Se non avanza proprio niente, alla riga 29 si esce. Le righe 30..33 servono a trovare un nome di file che sia differente da quello dei file già presenti, in modo da evitare sovrascritture. Questo sistema, ovviamente, non mette al riparo da upload contemporanei, ma sinceramente non era un mio requisito quando ho fatto lo script! La riga 34 raccoglie un filehandle da cui è possibile leggere il file inviato con l'upload; ci assicuriamo di effettuare una lettura binaria senza conversioni implicite (riga 36), apriamo il file di uscita (riga 37), lo impostiamo in modalità binaria (riga 38) e poi siamo pronti a fare la copia. Potevamo utilizzare altri sistemi, lo so, ma questo risolve! | |||