Newsgroup: it.comp.lang.perl
Titolo: cambio maiuscole/minuscole

Come faccio a fare la seguente operazione?
se $var ï¿œ una stringa tutta maiuscola o tutta minuscola cambia tutti i 
caratteri alfabetici in minuscolo, tranne quelli che non sono preceduti 
da un carattere alfabetico.
E.

 da un carattere alfabetico.
Dovresti fare qualche esempio. E' obbligatorio che la stringa sia tutta in
miuscolo o tutta in minuscolo? Se e' mista, non bisogna fare nulla?
Se c'e' un carattere alfabetico all'inizio, lo consideri come se fosse "non
preceduto da un carattere alfabetico" o no?

 da un carattere alfabetico.
Mi viene da pensare che sia piu' facile scartare il caso in cui la stringa
contenga un [a-z] piuttosto che verificare che sia composta solo da
maiuscole e punteggiatura.
Poi trasformi tutte le maiuscole in minuscole.
E poi rimetti in maiuscolo i caratteri non preceduti da [a-z]
In questo modo con due sostituzioni globali te la cavi.
Devi pero' definire in modo preferenziale il primo carattere della stringa
che non e' preceduto da niente.
-- 
questo articolo e` stato inviato via web dal servizio gratuito 
http://www.newsland.it/news
 segnala gli abusi ad 
ab...@newsland.it

 preceduto da un carattere alfabetico" o no?
Esatto.
E.

 che non e' preceduto da niente.
S�, algoritmicamente saprei come fare. Posso scrivere il codice in C, il 
mio problema � che non conosco abbastanza bene il perl per poter 
scrivere del codice perl decente. In C sarebbe:
void ConvertCase(char *s) {
	char *p;
	for (p=s;*p && !(isalpha(*p) && islower(*p));++p);
	if (*p) {
	  /// esiste almeno un carattere lowercase
	  for (p=s;*p && !(isalpha(*p) && isupper(*p));++p);
	  if (*p) {
	    /// esiste anche un carattere uppercase
	    return;
	  }
	}
	/// facciamo la conversione di case
         for (p=s;*p;++p) {
            if (isalpha(*p)) {
               if (p>s && isalpha(*(p-1)))
                  *p=tolower(*p);
               else
		  *p=toupper(*p);
            }
         }
     }
E.

?manu* wrote:

Stai tentando un titlecase brutale (ovvero in ogni parola della stringa 
mettere la prima lettera in maiuscolo e tutte le altre in minuscolo)?
Se ï¿œ cosï¿œ, ï¿œ abbastanza semplice:
$var =~ s{(\w+)}{\u\L$1}g;
che funziona anche se il case della stringa iniziale ï¿œ mIsTo e se le 
parole)non?sono!separate|da/spazi'ma,solo;da:segni.di-interpunzione 
(l'underscore non ricade tra questi, correttamente).
Sorgerebbe un piccolo problema con l'inglese, ove la lettera singola 
dopo l'apostrofo verrebbe messa maiuscola anch'essa, es.: "It'S" mentre 
sarebbe preferibile "It's".
La gestione di quest'ultimo caso ï¿œ lasciata come esercizio al lettore 
(non ï¿œ per sadismo, ma ci sono svariate soluzioni alternative, per 
scegliere tra le quali occorrono altre specifiche).
P.S.
Leggo solo adesso che il caso ï¿œ quello di "Nome Cognome": direi che la 
regex proposta va benissimo (effettua sempre la normalizzazione 
corretta, quale che sia il case originale della stringa).
Ciao
-Emanuele



Quindi se la stringa e' mista, non fare nulla; altrimenti, a prescindere,
cercare di trasformarla in Nome Cognome, dove la maiuscola e' cio' che sta
all'inizio, o dopo uno spazio o altro carattere (ad es. trattino).
Io non sono troppo d'accordo con la prima condizione, visto che in quel caso
cose come "Mario rossI" o "mARIO rOSSI" o "MARIO ROSSi" verrebbero
accettate.
Potresti provare questo:
perl -e 'my $n="GIAN MARIO ROSSI-BIANCHI"; 
$n=lc($n);$n=~s/((?<=[ -])|^)./\u$&/g;
print $n."\n"'
Comunque occhio perche' problemi di questo tipo possono diventare
rapidamente ingestibili.

 print $n."\n"'
Ti ringrazio, questa REGEXP mi ha dato molti spunti di 
approfondimento... Non capisco però cosa succede quando la parentesi 
corrisponde all'inizio riga (^), cosa contiene $&? Non dovrebbe essere 
vuoto?
E.

 $var =~ s{(\w+)}{\u\L$1}g;
Perchï¿œ utilizzi s{}{}g, invece di s///g ? Non ho trovato da nessuna 
parte una spiegazione di questa sintassi. Comunque ti ringrazio, ï¿œ 
proprio quello che mi serviva.
Per sapere se una stringa ï¿œ tutta maiuscola ï¿œ sensato usare il test:
if ($var eq uc($var)) {
   ...
}
o c'ï¿œ di meglio?
E.

 vuoto?
$&, da perlvar:  The string matched by the last successful pattern match. In
quella regexp, il match vero e proprio e' sempre quello dato dal
carattere ".", perche' tutto il resto e' o lookbehind, o beginning of
string. Se il beginning of string fa match, allora $& sara' cio' che e'
stato matchato da "^.", ma siccome ^ e' zero-width, di fatto hai solo il
carattere matchato dal ".". Se il lookbehind fa match, hai un'altra
zero-width assertion che non cambia "cosa" fa match nella regexp, che sara'
sempre il ".". (ps: occhio che se usi $1, $2 etc. invece le cose cambiano).
In sostanza, quella regexp e' un workaround per esprimere questo (che con
Perl - 5 almeno, non so il 6 - non si puo' fare):
(?<=([ -]|^)).
ossia, qualunque carattere preceduto da inizio stringa, o da uno dei
caratteri inclusi nella bracket expression. Perl considera quella
espressione variable-length lookbehind, che appunto non e' supportato.
Quello che e' supportato invece e' un'alternativa tra un lookbehind e
un'altra espressione, ed e' proprio quello che accade nel mio esempio.
Il "vantaggio" di usare questa soluzione e' che puoi personalizzare a
piacere i caratteri dopo i quali vuoi la maiuscola, basta aggiungerli alla
bracket expression.
L'altra soluzione proposta da Emanuele usa invece una differente logica che
si appoggia sulla definizione di \s, e tratta la stringa una parola alla
volta. Dipende da cosa vuoi fare esattamente, ma mi pare migliore
(@Emanuele: grazie, non sapevo si potesse combinare \u\L in quel modo). 
Btw, l'uso di s{}{} e' consentito da Perl. e' spiegato in perlop:
"Any non-alphanumeric, non-whitespace delimiter may replace the slashes.
...
If the PATTERN is delimited by bracketing quotes, the REPLACEMENT has its
own pair of quotes, which may or may not be bracketing quotes,
e.g., "s(foo)(bar)" or "s<foo>/bar/".
Spero di non avere sparato troppe corbellerie! :)

 o c'ï¿œ di meglio?
Semanticamente va bene, ma una regex sarebbe piï¿œ efficiente in quanto ci 
risparmierebbe la conversione:
if ( $var !~ /[a-z]/ ) { ... }
oppure, volendo rilevare l'uso anche delle accentate (italiane) minuscole:
if ( $var !~ /[a-zï¿œï¿œï¿œï¿œï¿œï¿œ]/ ) { ... }
Se volessimo perï¿œ gestire tutte le lettere con segni diacritici del set 
Unicode, le cose si complicherebbero (ma con CPAN ce la caveremmo 
ugualmente ;-)
(Oppure potremmo ricorrere all'uso dei locale, che peraltro influenzano 
anche la semantica della funzione uc()).
Infine, se posso darti un consiglio, se devi applicare una qualche 
normalizzazione alle tue stringhe, fallo brutalmente, senza stare a 
controllare prima quale fosse il case originario delle stringhe medesime 
(ti complica soltanto la vita).
Ciao
-Emanuele

 (ti complica soltanto la vita).
Non lo posso fare, perchï¿œ dev'essere possibile per l'utente forzare una 
determinata maiuscolizzazione. In ultima analisi sembra che tutto il 
problema sia dovuto agli scozzesi, che si lamentano se vedono scritto il 
loro nome come "Mac Donald" invece che "mac Donald". Perï¿œ non posso 
escludere ci siano altri casi simili. L'esempio italiano sarebbe "de 
Filippo", che alcuni vogliono scritto con "de" minuscolo.
E.

 Filippo", che alcuni vogliono scritto con "de" minuscolo.
Se questi casi particolari sono un numero limitato, potresti trattarli a
parte (ad esempio controllando prima se la stringa contiene la parola "mac"
o "de"). 
Ma mi rendo conto che se c'e' chi vuole "Mac" e chi vuole "mac", allora non
e' semplice (ah, l'avevo gia' detto :) ).

?manu* wrote:

Sï¿œ, avevo letto, ma conviene ugualmente applicare la normalizzazione e 
farli lamentare (altrimenti non sarebbe una normalizzazione).
Oppure, come ha suggerito antani, implementare un set di eccezioni sulla 
capitalizzazione di determinate stringhe (che perï¿œ faranno anch'esse 
parte delle regole di normalizzazione).
Il punto ï¿œ che ï¿œ impossibile riuscire a capire (in automatico) se la 
capitalizzazione utilizzata dall'utente ï¿œ voluta o ï¿œ frutto di un errore.
E anche laddove nessuno commettesse errori, dopo la normalizzazione 
rischi di ritrovarti con una capitalizzazione disomogenea (chi ha 
scritto "de Filippo" e chi invece preferisce "De Filippo") per cui a 
quel punto tanto vale accettare la stringa immessa dall'utente cosï¿œ 
com'ï¿œ senza effettuare alcuna trasformazione.
Almeno non sul case, mentre invece potresti normalizzare gli spazi (su 
quello non dovrebbero esserci eccezioni), se giï¿œ non lo fai.
Giusto per parlare, poi fai tu ;-)

 $var =~ s{(\w+)}{\u\L$1}g;
Ho un problema con questa REGEXP. Se gli dï¿œ "nicï¿œle" la trasforma in 
"Nicï¿œLe". Evidentemente considera le lettere accentate come non-testo.
E.

?manu* ha scritto:

Ho risolto (in parte) con
$var =~ s/(\s|^)([a-z])/$1\u$2/g
dico "in parte" perchï¿œ comunque mi pare che \u non sia in grado di fare 
l'uppercase dei caratteri alfabetici non ASCII. Ma almeno non li tratta 
come spazi...
E.

 "Nicï¿œLe". Evidentemente considera le lettere accentate come non-testo.
Si tratta solo di un problema di encoding.
Come accennavo nell'altro messaggio, ci sono svariati modi per gestirlo.
Partiamo dal piï¿œ semplice (che funziona se il tuo perl ï¿œ stato compilato 
col supporto PerlIO, come credo).
Scrivi il tuo programma (come /filtro/, che ï¿œ sempre la cosa migliore), 
in questo modo:
----------------------------------------
use strict;
use warnings;
#use utf8;
binmode STDIN , ':encoding(iso-8859-1)';
binmode STDOUT, ':encoding(iso-8859-1)';
while ( <> ) {
     s{(\w+)}{\u\L$1}g;
     print
}
-----------------------------------------
Questo nell'ipotesi che l'encoding della tua sorgente dati sia il 
famigerato Latin-1 (probabile, se sei su Windows).
Altrimenti ti tocca cambiare il primo binmode di conseguenza, ad es.:
binmode STDIN, ':encoding(utf-8)';
Il secondo binmode serve per la codifica dell'output: per i test (se sei 
su Windows) lascialo cosï¿œ, altrimenti anche quello cambialo come 
richiesto dalle tue specifiche.
In caso di UTF-8, il secondo binmode (ma solo quello), puoi anche 
scriverlo piï¿œ sinteticamente cosï¿œ:
binmode STDOUT, ':utf8';
Per i test, creati il file in.txt (codificato in LATIN-1) con i nomi uno 
per riga, e utilizza il programma sopra riportato (
titlecase.pl
) in 
questo modo:
cat in.txt | perl 
titlecase.pl
 > out.txt
Es.: se in in.txt hai "ï¿œï¿œï¿œrt", corrispondentemente in out.txt ti 
ritroverai: "ï¿œï¿œï¿œrt".
Se lo redirigi, dovresti anche riuscire a vedere l'output (corretto) in 
console, se setti correttamente l'encoding della console (in caso di 
Latin-1, esegui "chcp 850" e poi scegli il font Lucida console, non i 
font raster).
Ma non ci sperare troppo ;-)
Infine la linea:
#use utf8;
la dovresti decommentare se, per scopi di test, vuoi usare le tue 
stringhe sorgente direttamente nel codice.
Ciao