Tagliare, andare a capo, ricomporre stringhe di testo.

Da una richiesta di Giorgio Fisanotti e-mail giorgio_fisanotti@katamail.com, nasce questo interessante esercizio, che risulterà interessante per alcuni aspetti che aiuteranno coloro che si cimentano nella manipolazione di stringhe di testo.

il problema che Giorgio presentava è questo "....Si tratta di trasferire del testo da una textbox al foglio di  excel, questa operazione che mi riesce abbastanza bene ha però un problema e cioè, non mi restituisce il testo a capo corretto sul foglio. Non sono riuscito a intuire quale istruzione dare, affinchè il testo venisse ricopiato sulle celle del foglio tenendo conto del ritorno a capo. Infatti dopo 55 caratteri (da me impostati, potevano essere anche 100 o 150) il testo doveva proseguire sulla cella successiva (cosa che effettivamente avviene) ma doveva anche tener conto del ritorno a capo cioè deve intercettare che l'ultima parola venga copiata intera, o passata alla seconda cella se non ci stà intera nella prima..."
Giorgio invia questa sua routine associata ad un commandbutton di una UserForm. La routine funziona egregiamente, scompone tutto il contenuto della TextBox in stringhe di 55 caratteri, e le inserisce nelle celle, a scalare;  la riporto a beneficio di altri "pellegrini" :

  • Private Sub CommandButton1_Click()
     Worksheets("FOGLIO1").Range("A1") = Left(TextBox1.Text, 55)
        i = 1
        While (True)
            If Len(TextBox1.Text) > (55 * i) Then
                Worksheets("FOGLIO1").Range("A" & (i + 1)) = Mid _
    (TextBox1.Text, (55 * i) + 1, 55)
                i = i + 1
            Else
                Exit Sub
            End If
        Wend
    End Sub

     

In pratica le istruzioni leggono gruppi di 55 caratteri (compresi gli spazi vuoti) contenuti nella TextBox, e per ogni gruppo copiano il testo (55 caratteri per volta) in celle del foglio, iniziando dalla cella A1,  incrementando sia i successivi 55 caratteri nella textbox, sia la cella successiva, (con l'istruzione i = i + 1 e prendendo poi con Mid la parte di testo che inizierà da 55 * i +1, per 55 caratteri ancora, attraverso il ciclo While..Wend.

Premetto che la soluzione al problema non è affrontabile come su Word, che provvede in automatico a mandare a capo una parola se non entra tutta nella riga. Excel non è nato come elaboratore di testi, ma come foglio di calcolo, e le sue possibilità di gestione di stringhe di testo sono legate alle proprietà degli "oggetti" usati come "contenitori" di testo, quindi a celle del foglio di lavoro oppure a TextBox come in questo caso: per entrambi questi oggetti non sono disponibili istruzioni che permettano di gestire un comportamento analogo a ciò che avviene in Word. (ed escludo l'opzione "giustifica" applicabile al testo contenuto in una cella, che non provvede alla eventuale separazione sulla cella successiva, ma eventualmente ad aggiungere una riga nella stessa cella).

Inoltre, condizione determinante,  la routine così impostata non tiene conto di eventuali indici riga (peraltro non esistenti per celle o TextBox) ma vede come un tutt'uno il testo da trasferire, e lo "legge" a blocchi predefiniti (55 caratteri in questo caso).

Anche istruzioni basate sull'evento KeyPress della TextBox, istruzioni che leggendo il 55mo carattere mentre si digita, consentissero di mandare a capo la parola in quel momento digitata, se superasse il 55mo carattere senza sentire che viene digitato uno spazio (quindi sinonimo di una unica parola) non servirebbero a niente proprio per quanto detto al punto precedente.

Volendo quindi continuare a lavorare (senza rifare completamente la routine di Giorgio), rimane (secondo me) una unica possibilità: intervenire sulle celle del foglio DOPO che il testo è stato copiato dalla TextBox. E' evidente che l'esercizio a questo punto si presta ad interventi sulle stringhe di testo presenti nelle celle di un foglio, qualunque sia il modo in cui il testo vi compare (quindi anche se scritto direttamente nelle celle, e non copiatoci, purchè si mantengano gli spazi tra parola e parola anche si si va alla cella successiva).

E qui nascono i problemi: come poter identificare se la lettera alla fine di una stringa è una lettera facente parte di una parola che prosegue nella cella sottostante (quindi "troncata") , o invece è una lettera che appartiene alla fine di una parola regolarmente completa (quindi "intera"), contenuta nella cella?

Non penso che esista la possibilità di controllare la "completezza" di una parola, l'ideale sarebbe di poter disporre di un dizionario completo con il quale comparare se la parte finale di una stringa (rintracciabile leggendo la posizione dello spazio vuoto che precede l'ultima parola) unita alla parte iniziale della stringa della cella successiva (rintracciabile leggendo la posizione del primo spazio vuoto dopo la prima parola) formassero una parola contenuta nel dizionario, ecc. ecc.

Cosa questa fattibile, ma credo, impensabile da intraprendere per tutta una serie di appesantimenti del progetto che non sto neppure ad elencare.

Tornando alla realtà, dopo vari tentativi per cercare la soluzione al problema, ne è venuto fuori questo ragionamento:

una parola la possiamo considerare "troncata" quando l'ultima posizione di una stringa (x n. di caratteri, es. 55, quindi la 55.ma posizione) contiene una lettera. Questo vorrebbe dire che la lettera in quella posizione è una lettera che fa parte di una parola, che inizia alla fine (non importa da quale posizione) della stringa e prosegue sotto nella cella successiva, esempio la parola "melograno" in cui alla 55.ma posizione trovassimo la "o" di "melo" (e "grano", per effetto della routine vista sopra, si troverebbe nella cella sotto, senza spazio iniziale).
Si verificano comunque due altre possibilità, facendo la considerazione sopra:
che la 55.ma lettera sia una lettera unica ( tipo una "e" oppure una "a" o ancora una "o", esempio la "e" di una frase : "..siamo andati e...", oppure che sia la lettera finale di una parola. Ma in questo caso basta controllare se la riga sotto inizia per uno spazio vuoto, comunque contato come carattere dalla routine e quindi inserito a inizio cella seguente. Esiste poi un'ultima possibilità, cioè che la 55.ma posizione sia rappresentata da uno spazio vuoto, ma in questo caso la stringa successiva non inizia con uno spazio.Vediamo di spiegarci con alcuni esempi: evidenziati in rosso l'ipotetico 55.carattere, in blu lo spazio immediatamente successivo, se presente
 

stringhe

commenti

..La vispa Teresa avea tra l'erbetta
 a volo sorpresa gentil....
la a di erbetta è la fine di una parola intera ed esiste uno spazio iniziale nella cella successiva.
.......la vispa Teresa avea tra l'erbe
tta a volo sorpresa..
la e di erbe è la 55.ma lettera di una parola troncata perchè non esiste spazio ad inizio cella successiva
....vispa Teresa avea tra l'erbetta a
 volo sospresa gentil...
la 55.ma lettera a è una unica lettera ed esiste uno spazio ad inizio cella successiva
.La vispa Teresa avea tra l'erbetta
a volo sorpresa gentil
lo spazio vuoto è in 55.posizione e la cella successiva inizia senza spazio

ricordo che gli spazi vuoti vengono contati come caratteri e quindi potremo trovarli a fine stringa o inizio in conseguenza della lunghezza delle stringhe passate dalla routine.

Bene, ora possiamo impostare un ciclo che scorrendo le celle della colonna A, nell'intervallo occupato, controllino se l'ultimo carattere della stringa (che è lunga 55 caratteri) è diverso da vuoto, (quindi è una lettera) e se il primo carattere della stringa nella cella successiva è vuoto o contiene un carattere. Se verrà riscontrata una parola troncata, taglieremo la parte finale delle stringa che fa parte di una parola troncata e la aggiungeremo all'inizio della cella successiva: in questo modo "uniamo" le due parti della parola troncata.

A questo punto però perderemo la lunghezza stringa che non sarà più di 55 caratteri per tutte le celle, come l'istruzione di Giorgio aveva predisposto, ma necessariamente modificata per lo spostamento del testo che sarà avvenuto. Otterremo comunque che ogni stringa conterrà parole intere e non troncate, che è  l'obiettivo che soddisfa le richiesta di Giorgio. Intanto vediamo le istruzioni che consentono di "ricomporre" parole troncate.

In Excel, per fare un "taglia e incolla" di una parte di una stringa di testo, possiamo utilizzare due Metodi dell'"Oggetto" Characters : Delete e Insert. Ritenendo un pò più complicato utilizzare Insert in questo caso, e visto che comunque dobbiamo "leggere" la parte di testo da copiare, ho sfruttato il Metodo Characters dell'"Oggetto" Range, che ci consente, impostato un punto di inizio all'interno della stringa (Start), e la lunghezza dei caratteri che ci interessano (Length), di "copiare" questi caratteri, mentre sfrutteremo l'"Oggetto" Characters al quale applichiamo il metodo Delete, per cancellare la parte del testo che avremo copiato nella cella sottostante. Predisponiamo in un modulo standard la nostra routine (ma potrà essere lo stesso modulo della UserForm) e la chiameremo alla fine dell'istruzione di Giorgio, dopo Else e prima di Exit Sub oppure potremo chiamare la routine Controller da un altro pulsante, in successione:

Sub Controller()
Dim Ri As Integer 
'dichiarazione variabili
Dim N As Integer
Dim W As Integer
Dim U As Integer
Dim CC As Integer

'nella necessità di reperire su quale intervallo di celle intervenire, è possibile ricorrere ai vari metodi conosciuti per 'definire l'intervallo, io ho scelto di contare le righe occupate dal testo copiato dalla TextBox, per ottenere un 'valore da usare (variabile Ri) come limite superiore di un ciclo For..Next che scorra tutte le celle dell'intervallo
Ri = Sheets(1).Range(Range("A1"), Range("A1").End(xlDown)).Rows.Count
For N = 1 To Ri - 1 
'iniziamo il ciclo che leggerà tutte le celle fino alla penultima (variabile N)
Dim Ole As String    
'dichiarazione della variabile "ole" come stringa
Ole = Cells(N, 1).Value
  'Ole sarà uguale al valore (testo) della Cella N, colonna 1
W = Len(Ole) 
'con la variabile W prendiamo la lunghezza del testo

'sotto: ora si controlla SE l'ultimo carattere (Right(Ole, 1)) della stringa è diverso da vuoto, cioè è una lettera, E SE 'il primo carattere della cella sottostante (Left(Cells(N + 1, 1), 1)) è uguale a vuoto. Se sui verificano entrambe le 'due condizioni vuol dire che non esiste nella fine della stringa una parola troncata
If Right(Ole, 1) <> " " And Left(Cells(N + 1, 1), 1) = " " Then
'allora ci spostiamo all'indicatore 10
GoTo 10
End If
10:
 'indicatore di riga

'ora si verifica SE : sia l'ultimo carattere delle stringa, sia il primo carattere delle cella sotto sono diversi da vuoto, vuol dire che si tratta di una parola troncata, allora...

If Right(Ole, 1) <> " " And Left(Cells(N + 1, 1), 1) <> " " Then


U = InStrRev(Ole, " ")
   'con "U" prendiamo la posizione del primo spazio vuoto che si trova nella stringa partendo 'dalla FINE della stringa (InStrRev), che ci servirà per sapere da quale punto della stringa prendere le ultime lettere

Cells(N, 1).Value = Cells(N, 1).Text   'rendiamo il contenuto "testo" della cella come "valore" per necessità legate 'all'uso del metodo Characters
CC = W - U    'con CC otteniamo la differenza tra la lunghezza della stringa "W" e la posizione ottenuta con "U", 'ed otteniamo la quantità di caratteri che dovranno essere copiati nella riga sottostante oltre che a "toglierli" dalla 'stringa.

'sotto: ora "copiamo" nella cella sottostante, la parte della parola che inizia dalla posizione "U" per la lunghezza 'rappresentata da "CC" +1, concatenandola ( & ) con il testo già esistente nella cella: questo ci consente di "unire" 'le due parti della parola
Cells(N + 1, 1).Value = Trim(Cells(N, 1).Characters(Start:=U, Length:=CC + 1).Text) & Cells(N + 1, 1).Value

Cells(N, 1) = LTrim(Cells(N, 1))
 'poi eliminiamo eventuali spazi ad inizio cella, per rendere allineato il testo

'sotto: ora sfruttiamo l'oggetto Characters, indicando da quale posizione iniziale e fino a dove, cancellare i caratteri
With Cells(N, 1)
.Characters(U, CC + 1).Delete
End With
End If
Next

End Sub

Ed ora vediamo un esempio delle due routine, in due fasi: la prima immagine mostra la UserForm dove nella TextBox1 è stato scritto del testo, e nella colonna A, l'effetto della routine di Giorgio, per l'occasione ridotta ad un esempio impostato su 15 caratteri anzichè 55 (tanto è lo stesso, come esempio); si nota chiaramente la presenza di parole "troncate" e fatte proseguire sulla cella sottostante:

e questo invece ciò che otteniamo lanciando la routine Sub Controller()

Come si nota, le parola "troncate" sono state unite, ma si è persa necessariamente la lunghezza di 15 caratteri per cella; oserei dire che non si può avere la botte piena e la moglie ubriaca. In questo esempio comunque si nota maggiormente la diversa lunghezza nelle stringhe visto che ho impostato un gruppo di soli 15 caratteri; se le stringhe fossero state impostate su 55 caratteri, sarebbero apparse meno diverse in lunghezza.

Se vi ho forniti utili suggerimenti, meglio così.

Buon Lavoro.

prelevato sul sito www.ennius.altervista.org