I 3 dizionari di NVDA: Terza parte.

I 3 dizionari di NVDA, terza parte.
Di Adriano Barbieri.
*********
Adesso che abbiamo anche compreso cosa sono i gruppi tra parentesi tonde e come sfruttarne la loro potenzialità, per crearci il nostro "asterisco" personalizzato, in questo articolo proverò ad elencare alcuni metodi per matchare e manipolare la vocalizzazione di alcuni tipi di vocaboli.
Inoltre, proverò a descrivere alcune tecniche utili che consentono di invertire, filtrare, e non far vocalizzare parti di una parola e anche, quando possibile, costruire un'espressione regolare che da sola è in grado di matchare anche alcune eventuali varianti di un vocabolo.
La peculiarità delle Regex, al pari di tutti i linguaggi di programmazione,come già detto nella seconda parte di questa serie di articoli, è consentire di memorizzare parti di una stringa di matching, dandoci poi la possibilità di richiamarne il valore memorizzato semplicemente riferendoci al numero logico assegnato al gruppo.
Vi ricordo che il numero logico assegnato a un gruppo è un valore rappresentato dai numeri "1, 2, 3", eccetera, abbinato ad ogni gruppo rappresentato dalla coppia di parentesi tonde"()" aperta e chiusa, , da sinistra verso destra: Tenete presente che occorre contare anche i gruppi che potrebbero essere stati nidificati all'interno di un altro gruppo, perché anch'essi hanno un loro numero logico abbinato.
E’ possibile quindi richiamare e far vocalizzare il contenuto del matching dei gruppi presenti nella nostra espressione regolare, ma non c'è una regola che imponga il richiamo in un ordine definito, ciò vuol dire che possiamo farlo nell'ordine a nostra scelta, ad esempio:
• Sempre dal dizionario temporaneo, facciamo invio su aggiungi.
• Nel primo campo digitiamo: "(N)(V)(D)(A)", come sempre senza le virgolette. Fate attenzione alle parentesi tonde.
• I 4 gruppi della Regex memorizzeranno ognuno la lettera assegnatagli.
Provate ora a richiamare i gruppi inserendo nel campo in sostituzione il numero logico precedendolo dalla barra diagonale rovesciata, così: "\1\2\3\4", notare che lo spazio tra un richiamo e l'altro non è obbligatorio, è stato inserito solo per generare uno "spelling", altrimenti la parola verrebbe letta tutta di un fiato.
Questo anche a dimostrare che è possibile l'inserimento di caratteri o intere parole, nonché punteggiature e/o spaziatura fra i vari richiami.
Il modulo dizionario è munito anche di due caselle di controllo in aggiunta, mi raccomando di marcarle entrambePer seguire gli esempi in questo articolo!
Così facendo se scriverete: "NVDA" da blocco note e farete vocalizzare la riga digitata, non si avvertirà nulla di strano, ma provate a mescolare l'ordine dei numeri logici presenti nel campo sostituzione e provate a far vocalizzare la riga precedentemente editata e vedrete che avrete una lettura molto diversa.
Perché funzioni, ovviamente serve che il matching sia true, cioè vero, quindi è neccessario che il testo editato nel blocco note corrisponda appieno a ciò che è stato previsto nel campo di ricerca, sia per quanto riguarda l'ordine, sia per quanto riguarda tipo e numero dei caratteri. Ulteriori combinazioni di caratteri digitati manterranno il loro stato originale e non verranno invertite, e verranno lette così come sono state digitate.
Nulla ci impedisce a questo punto, di sfruttare i gruppi alla stregua di filtri. Potremmo far sì che venga vocalizzata solo una parte del matching, scartando ciò che non serve, provate ad esempio a togliere il richiamo di uno o due gruppi dal campo sostituzione, oppure ripetete alcuni richiami, la lettera o la parola memorizzata nei gruppi corrispondenti verrà ripetuta per ogni richiamo abbinato al gruppo.
Fate poi vocalizzare la riga precedentemente digitata nel blocco note e noterete le differenze.
Con un poco di fantasia, è possibile immaginare che il sistema ci permetta di invertire e/o filtrare e/o far ripetere intere parole e/o parti di esse.
Vediamo adesso alcuni metodi per matchare una parola ambigua, per il semplice fatto che la stessa parola, a seconda del contesto cambia la posizione dell'accento, nella lingua italiana ce nè un'infinità, eccone un piccolo esempio:
• "Desidèri, desìderi".
• "Rètina, retìna".
• "Pòrtali, portàli".
• "Tèndine, tendìne.
Un particolare che ci consente di identificare il contesto, anche se a volte incorrono eccezioni, è l'articolo oppure è l'aggettivo che precede la parola, in altri casi la congiunzione che la segue, oppure la parola adiacente.
Innanzitutto occorre capire la nostra sintesi vocale cosa fa già da sola, perché molte sintesi vocali hanno nel proprio "motore" degli algoritmi dedicati a interpretare nel modo migliore quelle parole ambigue. Però, siccome molto spesso non ci soddisfano i risultati, perché non è possibile prevedere tutto, accade che qualche cosa viene tralasciata per non perdere tempo oppure si opta per una via di mezzo, quindi, dobbiamo per forza intervenire.
Ora inizieremo a sfruttare interamente le potenzialità delle espressioni regolari; negli esempi presenti nei precedenti due articoli usavamo parte di testo fisso nel campo in sostituzione.
Questo però non è il modo più efficace, per motivi che vedremo nell'articolo successivo.
Noi "smonteremo" letteralmente la parola da matchare, isolando la vocale da sostituire, ma memorizzando tutte le parti che ci occorrono.
Ecco qualche esempio:
• "Mi fa male il tendine".
• "Le tendine erano azzurrine".
Prendiamo ad esempio questa ultima parola, riferita al tendine di Achille... quello che arriva fino al tallone.... "Tendine", può essere anche al plurale: "Tendini" e quindi bisognerà tenerlo presente.
la nostra Regex dovrà riconoscere dall'articolo se si tratta del tèndine o di tendìne. Quindi, potremmo creare un gruppo di controllo da anteporre al gruppo con la parola da matchare in oggetto, contenente una lista degli articoli determinativi ammessi, basterà matchare l'ultima lettera più uno spazio che dovrebbe essere parte dell possibile articolo che precede la parola "Tendin", un altro gruppo di controllo invece, si occuperà di matchare l'ultima vocale della parola, per determinare se essa sarà singolare o plurale, una cosa come questa ad esempio:
#Tendine/i.
"([iIlL]\W+[tT])[eE](NDIN|ndin)([eEiI]\W)", e in sostituzione: "\1é\2\3".
Il primo gruppo verifica la presenza di parole terminanti con le lettere contenute nella classe tra parentesi quadre, che vi sia un separatore, o più di uno, e memorizza anche l'iniziale della parola, la "t".
Il secondo memorizza la parte di parola "ndin", il terzo gruppo memorizza la vocale, una delle permessa dalla classe tra parentesi quadre; il carattere "\W" (barra rovesciata doppia vu maiuscola) chiude la nostra espressione regolare.
Un sistema un po' blando, ma che permette di vocalizzare correttamente:
• "al tendine".
• "col tendine".
• "dal tendine".
• "del tendine".
• "nel tendine".
• "sul tendine".
Ma anche:
• "ai tendini"
• "dai tendini"
• "dei tendini"
• "coi tendini"
• "nei tendini"
• "sui tendini"
Insomma qualsiasi parola, che termini con uno dei caratteri racchiusi nella prima classe fra parentesi quadre contenuta nel primo gruppo dell'espressione regolare. Da notare anche che questi caratteri devono avere un carattere separatore prima della seconda classe nel primo gruppo, ed è indicato dal carattere speciale: "\W"(barra diagonale rovesciata doppia vu maiuscola), ma anche un "\s"(barra diagonale rovesciata s minuscola) andrebbe bene, questa indica uno spazio.
La Regex però accetterà anche una frase del genere: "Questi tendini mi fanno male".
Ma non questa: "Questo tendine mi fa male".
Sarà necessario aggiungere nella classe del primo gruppo la vocale finale, minuscola e maiuscola "oO", dell'aggettivo dimostrativo perché venga matchata correttamente.
Questo sistema è funzionale nella sua semplicità, ma lascia passare parole, qualsiasi parola che termini con una delle vocali ammesse nella classe, anche se insensate come:
• "fdghfi tendini".
• "dsfhfl tendine".
Per affinare la nostra Regex occorrerebbe aggiungere un ulteriore gruppo a sinistra, che consenta il matching solo se la frase è a inizio riga o preceduta da un carattere separatore come uno spazio, una tabulazione, una punteggiatura, eccetera.
Io normalmente uso questo gruppo: "(\b)", in altri casi, dove ci siano delle accentate, o apostrofi, è più indicato "(\W|^)".
Se questo gruppo lo mettiamo all'inizio di una Regex, esso, nella maggior parte dei casi, impedisce che vi siano caratteri appiccicati sulla immediata sinistra della frase da matchare. Se il gruppo poi lo si fa seguire da un'altro che contenga i nostri articoli o /e aggettivi, non saranno ammesse confusioni di sorta.
La Regex allora potrebbe esser così:
"(\W|^)([iI]|[aAiIuU][iIlLnN]|[dDcCnNsS][aAeEoOuU][iIlL])(\s+[tT])[eE](NDIN|ndin)([eEiI]\W)", e in sostituzione: "\1\2\3é\4\5".
Oppure così:
"(|b)([iI]|[aAiIuU][iIlLnN]|[dDcCnNsS][aAeEoOuU][iIlL])(\s+[tT])[eE](NDIN|ndin)([eEiI]\W)", e in sostituzione: "\1\2\3é\4\5".

Se fate caso alle classi contenute nel secondo gruppo, ci sono gli articoli determinativi ammessi, sia in minuscolo, sia in maiuscolo: "i, al, il, un, dal, del, col, nel, nei, sul".
L'ultimo gruppo, in entrambi gli esempi, si occupa di far terminare la nostra parola "Tendin" con la sola vocale che determinerà se sarà singolare o plurale.
Ovviamente se la nostra parola dovesse essere priva di articolo alla sua sinistra, il matching della Regex sarà false e non verrà eseguita.
Le classi fra parentesi quadre, in questo esempio, permettono il matching di un solo carattere contenuto al loro interno, ma se una classe è affiancata da un altra classe, il carattere si somma al successivo formando la parola come se fosse così: "[pP]+[aA]+[rR]+[oO]+[lL]+[aA]".
Il matching fra multiple combinazioni di classi multiple ha termine appena viene raggiunto il carattere "|"(linea verticale che significa or), il quale determina una alternativa e di conseguenza un'altra serie di combinazioni di classi ammesse nel matching.
Infatti, alcuni articoli determinativi sono di un carattere, altri composti da due, e altri da tre caratteri
Notare che, per limitare la lunghezza della riga, non ho inserito il controllo sugli aggettivi dimostrativi "questi e questo", ma una seconda Regex come quella che segue risolverebbe il problema:
#Tendine/i/questi/o.
"(\b[qQ])(UEST|uest)([iIoO]\s+[tT])[eE](NDIN|ndin)([eEiI]\W)", e in sostituzione: "\1\2\3é\4\5".
Vediamo ora un altro metodo di matching che sfrutta il carattere speciale "."(punto fermo), indicante che è accettabile un carattere ignoto. E se volessimo un numero definito di caratteri ignoti? Ecco un esempio anche se parziale:
La parola "Fracassare", quante varianti ha?
• "Fracassarmela".
• "Fracassarmele".
• "Fracassarmeli".
• "Fracassarmelo"...
Quì ce la caveremo con questa Regex:
#Fracassarmela/e/i/o.
"(FRACASS|[fF]racass)[aA](RMEL|rmel)([aAeEiIoO]\W)", e in sostituzione: "\1à\2\3".
Funzionerebbe anche così:
#Fracassarmela/e/i/o.
"(FRACASS|[ff]racass)[aA](RMEL|rmel)(.\W)", e in sostituzione: "\1à\2\3".

Abbiamo sostituito l'intera classe dell'ultimo gruppo con un singolo "." (punto fermo). In pratica, nel caso sopracitato ci serve un solo carattere per l'identificazione della parola, la vocale finale della parola stessa, e si sa che sono solo quattro in tutto, anche se il solo punto fermo lascerà passare altri caratteri oltre alle sole vocali richieste, ma può andar bene ugualmente, dipende da quanto si vuol essere pignoli.
Ma se fosse invece:
• "Fracassartela"
• "Fracassartele"
• "Fracassarteli"
• "Fracassartelo"
Be'! un'altra regex in supporto come questa andrebbe bene:
"(FRACASS|[fF]racass)[aA](RTEL|rtel)(.\W)", e in sostituzione: "\1à\2\3".
E siamo a 2 Regex!ma se fosse:
• "Fracassargliela"
• "Fracassarmelo"
• "fracassarsele"
• "fracassarglieli"

In questo particolare caso, considerando che la parola "Fracassar" e ricorsiva in quasi tutte le varianti, possiamo risolvere la questione con una sola Regex che preveda un numero variabile di caratteri ignoti, e ci vengono in soccorso le parentesi graffe.
Esse, come detto nel precedente articolo, ci permettono di definire un limite minimo e massimo di rippetizioni dell'ultimo carattere che le precede, anche se si tratta di un carattere speciale, il punto fermo, come nel nostro caso, ed ecco cosa otteniamo:
#Fracassargliela/mela/sela/tela/e/i/o.
"(FRACASS|[fF]racass)[aA]([rR])(.{4,6}\W)", e in sostituzione: "\1à\2\3".

La riga di commento dopo il carattere "#"elenca tutto ciò che la regex dovrebbe accettare in grandi linee, quindi, se il matching della parola: "fracassar", sarà true, saranno ammessi in aggiunta un minimo di quattro caratteri fino ad un massimo di sei caratteri ignoti, appiccicati alla parola "fracassar".
Purtroppo però, anche "fracassardxdghf" sarà accettata, ma contiamo che nessuno userà mai una parola così palesemente errata, sicuri di non sbagliare.
Ecco un'altro esempio:
#Precluderla/e/i/o/gli.
"(PRECL\[pP]recl)[uU](DER|der)(.{2,3}\W)", e in sostituzione: "\1ù\2\3".

Vediamo un altro metodo che sfrutta ancora le classi, ossia un gruppo di caratteri o numeri racchiusi fra parentesi quadre.
Ne abbiamo già parlato, e abbiamo visto che per ogni classe è preso in considerazione solo un carattere alla volta tra quelli presenti nella lista per ogni classe, e sottolineo per ogni classe.
Questo significa che, affiancare delle classi tra di loro, ci consente di formare delle parole intere, esattamente come se ad esempio scrivessimo: "nvda", o "[n][v][d][a]", con il vantaggio però, che le classi ci consentono di inserire delle varianti, tramite l'aggiunta di altri caratteri all'interno di ogniuna,creando delle liste ad esempio così:
"[nN][vV][dD][aA]".
In questo modo abbiamo detto che la parola "nvda" è ammessa sia in minuscolo e anche in maiuscolo.
Comodo per matchare parole un po' più lunghe come ad esempio questa:
#Grondano/ino.
"(GR|[gG]r)[oO](ND|nd)([aAiI][nN][oO]\W)", e in sostituzione: "\1ò\2\3".

Come potete vedere, la Regex appena descritta consente di matchare:
• "Grondano"
• "Grondino"

Oppure quest'altra un po' più complessa, ma sempre sfruttante lo stesso principio delle classi affiancate:
#Grassocce/i/ia/io.
"(GRASS|[gG]rass)[oO](CC|cc)(([eEiI]|[iI][aAoO])\W)", e in sostituzione: "\1ò\2\3".
Questa Regex consente di matchare:
• "Grassocce"
• "Grassocci"
• "Grassoccia"
• "Grassoccio"

Un ultimo esempio:

#Tingerci/mi/ne/si/ti/vi/gli.
"([tT])[iI](NGER|nger)([cCmMnNsStTvV][eEiI]|(GLI|gli)\W)", e in sostituzione: "\1ì\2\3".
Questa Regex consente di matchare:
• "Tingerci"
• "Tingermi"
• "Tingerne"
• "Tingersi"
• "Tingerti"
• "Tingervi"
• "Tingergli"

Dalla stesura del primo articolo dedicato ai dizionari di Nvda è passato un po' di tempo, in quel periodo non ero ancora molto pratico a gestire le punteggiature per matchare le famose emoticons, le faccine che spesso vengono usate nei messaggi di posta elettronica o nelle sezioni di chat.
Confesso che crearle mi fece perdere parecchio tempo e arrivai a dichiarare che a causa dell'ambiguità con l'uso di caratteri speciali usati nelle Regex la cosa era stata molto difficoltosa, ma ora mi sono ricreduto. Infatti, combinando la tecnica delle classi affiancate e con l'uso delle parentesi graffe la cosa è semplicissima.
Occorre solo mettere le cose nel giusto ordine, aggiungendo,se necessario, delle classi se l'emoticon dovesse essere composta da più di due o tre caratteri, come invece prevede l'esempio che segue.
Ecco come ho creato la faccina:
# :-)) Faccina che ride di gusto.
"(\s|^)(:([\-]|)([)]{2,})\B)", e in sostituzione: " faccina che ride di gusto ".

Il primo gruppo si occupa di verificare, che la faccina abbia alla sua sinistra uno spazio, indicato dal metacarattere: "\s" (barra diagonale rovesciata s minuscola), oppure che la nostra faccina sia ad inizio riga, indicato dall apice "'" (accento circonflesso).
Il secondo gruppo contiene le classi che compongono la faccina vera e propria. Si può notare il carattere ":" (due punti) per gli occhietti, seguito da una classe contenente il "-"(trattino),che è preceduto dalla "\"(barra diagonale rovesciata) ad indicare che il trattino deve essere trattato alla stregua di semplice carattere e non come carattere speciale (il nasino), seguita dalla "|" (barra verticale), che sta a indicare, perché sprovvista di alternative, che il trattino può anche essere omesso; segue un'altra classe che contiene la ")"(parentesi chiusa), il ghigno sorridente della nostra emoticon.
Subito seguita da un gruppo di parentesi graffe, ad indicare le ripetizioni ammesse del carattere che le precedono; in questo caso la parentesi chiusa del sorriso è ammessa da un minimo di due ripetizioni fino a fine riga perché non è stato indicato il limite massimo dopo la (virgola).
Le due alternative sono state raggruppate fra parentesi tonde senza gli occhietti. I due punti che li raffigurano sono stati inseriti una sola volta subito prima delle due alternative, cioè appena all'inizio e subito all'interno del secondo gruppo di parentesi tonde. Ovviamente sostituire gli occhietti raffigurati dai ":"(due punti) con, ad esempio, il ";" (punto e virgola) per indicare la strizzatina d'occhio, sarà un attimo. Infine, l'ultimo gruppo di chiusura della Regex controlla che non vi sia appiccicato nient'altro, tranne un eventuale spazio.

Per non generare confusioni con un'altra Regex simile che intercetta la faccina sorridente, cioè quella composta da una sola parentesi tonda chiusa, è necessario metterla dopo di quella appena descritta.
Questo per dire che le Regex, in alcune eccezioni non devono essere sempre accodate una all'altra indiscriminatamente; sì, è giusto mantenere per quanto possibile un ordine alfabetico inanzitutto, ma a volte per evitare conflitti di matching è neccessario dare la precedenza alle Regex che elaborano cose molto somiglianti fra loro, questo perché i dizionari di Nvda vengono letti e memorizzati dal modulo,l'ho già detto e lo ribadisco, sempre in cascata, dall'alto verso il basso.

Segue con: "Differenze tra sintesi vocali, problematiche e indicazioni per la creazione di un dizionario specifico".

Adriano Barbieri.