Come analizzare i files di testo con il Parsing.

001

Chi di noi non ha mai avuto l'esigenza di effettuare controlli sui propri documenti, scritti ed elaborati, in maniera alternativa rispetto agli altri software? Controllare frasi, parole ed impostare regole specifiche non è poi così complicato. L'articolo tratta di un metodo, abbastanza semplice, che ha la finalità di eseguire delle analisi personalizzate sui file di testo, al fine di trovare eventuali errori o calcolare parametri statistici utili ed interessanti. Un'occasione in più per affinare la qualità dei propri testi.

In inglese, il verbo "to parse" significa "analizzare". Esistono, nel mondo informatico, tante tipologie di parser: analizzatori sintattici, semantici, aritmetici, ed altri.
In questo articolo realizzeremo, con l'aiuto del linguaggio Harbour, un piccolo analizzatore, con cui controllare i propri elaborati e i propri scritti.
Vedremo come questo strumento potrà tornare utile agli autori di articoli (proprio la nostra categoria...) nel cercare difetti nei propri testi che altri controllori e tools non riescono a trovare.
Requisito fondamentale è quello di conoscere un minimo di programmazione procedurale e di aver installato, sul proprio PC, il compilatore Harbour.
Per chi fosse interessato a scoprire le potenzialità del linguaggio in questione può visionare il precedente blog a questo indirizzo.
Harbour può essere scaricato in formato sorgente oppure eseguibile, sia per piattaforma Windows che per Linux (in quest'ultimo caso deve essere obbligatoriamente compilato). Il link attuale del compilatore è questo.
In rete esistono tantissime guide, in molte lingue, che descrivono il funzionamento di Harbour assieme alle sue funzioni, veramente numerose.
I linguaggi che offrono un supporto sul parsing dei file di testo non sono molti; forse il più rappresentativo è il Python, che mette a disposizione anche tante librerie per analizzare i formati HTML, XML, e tanti altri.

Prerogative del parser

L'articolo illustrerà alcune idee su come effettuare un'analisi di un file di testo, al fine di cercare eventuali errori, non riconoscibili da altri editor, come il Word di MS-Office o il Writer di LibreOffice. Si tratta, quindi, di implementare alcune possibilità migliorative, per i nostri scritti che, sommandosi a quelle offerte dagli editor appena menzionati, renderanno i propri lavori ancor più corretti.
Una possibilità, dunque, aggiuntiva che consente, agli scrittori, agli autori di articoli, agli studenti e ai giornalisti, di filtrare al massimo i propri testi.
In aggiunta, il parsing effettuato con gli algoritmi utilizzati nell'articolo, consente anche di conteggiare le parole secondo particolari criteri, dando luogo ad una elaborazione statistica molto interessante e diversa dal solito.
Il parser è, naturalmente, personalizzabile e programmabile. Occorre, certo, un po' di fatica aggiuntiva, in quanto esso deve essere scritto e preparato con un opportuno linguaggio di programmazione. Ma è proprio questo il punto di forza: un bravo programmatore può apportare tutte quelle migliorie e caratteristiche aggiuntive in modo da adattarlo perfettamente alle proprie esigenze lavorative.
L'articolo costituisce, pertanto un punto di inizio, uno spunto, per iniziare a concepire, in seguito, un parser ben più complesso e completo.
In linea di massima, il nostro parser dovrebbe poter "scovare" i seguenti errori:
  • parole troppo vicine (ripetizioni);
  • parola troppo ripetuta:
In aggiunta, esso dovrebbe eseguire le seguenti ricerche statistiche:
  • conteggio di parole (classifica);
  • conteggio di coniugazioni ("are", "ere", "ire");
Ovviamente il nostro parser non effettua il controllo delle parole in un dizionario, operazione che, sia il Word che il Writer, eseguono fin troppo bene. In ogni caso, anche questa opzione può essere implementata, dal momento che il programmatore ha la piena indipendenza ed il pieno controllo della piattaforma operativa.

Come Harbour gestisce il Parsing

Il linguaggio Harbour mette a disposizione alcune funzioni straordinarie che trattano, in maniera molto semplice, il testo ed in particolare il processo di isolamento della singola parola. Un testo è, infatti, un insieme di parole (token) separate tra loro attraverso dei caratteri speciali come, ad esempio, lo spazio, i segni di punteggiatura ed altri equivalenti.
Scrivendo di proprio pugno un semplice processo di "tokenizzazione" si va, inevitabilmente, a sbattere contro l'ostacolo più grosso, ossia quello di isolare le parole dai diversi elementi di separazione.
Usando invece le funzioni già implementate in Harbour, il programmatore può usufruire della immediatezza delle operazioni nonché, aspetto non meno importante, della sicurezza e dell'affidabilità degli algoritmi.
Le funzioni principali del nuovo "core" di Harbour sono le seguenti:
  • AtToken;
  • NumToken;
  • RestToken;
  • SaveToken;
  • Token;
  • TokenAt;
  • TokenEnd;
  • TokenExit;
  • TokenInit;
  • TokenLower;
  • TokenNext;
  • TokenNum;
  • TokenSep;
  • TokenUpper.
Ovviamente non saranno studiate tutte ma solo quelle più importanti e di interesse ed utilità maggiori.

I caratteri separatori

Come detto prima, un testo è l'insieme di numeri e parole (solitamente di senso compiuto e presenti in un dizionario) separate tra loro mediante uno o più caratteri speciali di separazione. Le funzioni di Harbour, per default, utilizzano i seguenti caratteri per specificare il "distacco" tra una parola e la successiva:
  • Chr(0)
  • Chr(9)
  • Chr(10)
  • Chr(13)
  • Chr(26)
  • hb_BChar(138)
  • hb_BChar(141)
  • Chr( 32 )
ed inoltre i seguenti caratteri: ,.;:!\?/\\<>()#&%+-*
Il programmatore può, ovviamente, personalizzare la lista dei "tokenizer" (o delimitatori) secondo le proprie esigenze. Nel nostro caso aggiungeremo, ai caratteri delimitatori preimpostati, anche quelli con codice ASCII 34 e 39, corrispondenti ai doppi e ai singoli apici.

La funzione Token()

Sintassi: Token( <cString>, [<cTokenizer>], [<nTokenCount], [<nSkipWidth>],[<@cPreTokenSep>], [<@cPostTokenSep>] ) -> cToken
E' la più importante per la gestione del parsing di un testo. La funzione estrae l'n.simo token dalla stringa passata come parametro (<cString>). I token devono essere separati tra loro dai caratteri specificati dal parametro <cTokenizer>.
Per esempio, l'estrazione del quinto token da una stringa, avviene con la seguente funzione:
t := token("Buon giorno e buon appetito a tutti" ,, 5)
che restituisce la stringa "appetito".

La funzione NumToken()

Strettamente legata alla precedente, è una funzione che restituisce, semplicemente, la quantità di token presenti in una stringa. Nei nostri esempi la sua funzionalità è utile per impostare un ciclo di lettura che itera il processo dal primo all'ultimo token.

Memorizzare il testo in una stringa di Harbour

L'operazione indispensabile che rende possibile il processo di parsing è quella della memorizzazione del testo da elaborare in una stringa, gestibile da Harbour. Tale scopo si ottiene attraverso due semplici operazioni:
  1. Memorizzazione del proprio elaborato in un file di testo (con estensione TXT);
  2. Assegnazione del contenuto del file di testo ad una variabile di tipo stringa di Harbour.
Tale assegnazione si effettua mediante la funzione "hb_MemoRead" che, in maniera del tutto trasparente, memorizza un file di testo, anche di grandi dimensioni, in una variabile. Gli esempi che seguiranno chiariranno bene tale concetto.

Primo esempio: otteniamo e visualizziamo la lista delle singole parole del nostro articolo

Il primo esempio ha il semplice scopo di listare a video tutte le parole presenti nel nostro elaborato, senza il "disturbo" dei caratteri di separazione. Tale prova ha lo scopo di far prendere confidenza con le operazioni di parsing e della gestione dei token. Segue il listato e poi l'opportuno e chiaro commento:
REQUEST HB_CODEPAGE_ITISO
PROCEDURE Main()
   LOCAL cStringa, k, cDelimitatore, nNumeroToken
   cls
   hb_SetCodepage( "ITISO" )
   cDelimitatore := Chr( 0 ) + Chr( 9 ) + Chr( 10 ) + Chr( 13 ) + ;
                    Chr( 26 ) + hb_BChar( 138 ) + hb_BChar( 141 ) + ;
                    Chr( 32 ) + ",.;:!\?/\\<>()#&%+-*" + ;
                    Chr( 34 ) + Chr( 39 )
   cStringa := hb_MemoRead( "prova.txt" )
   nNumeroToken := NumToken( cStringa, cDelimitatore )
   FOR k = 1 TO nNumeroToken
      ? k, Token( cStringa, cDelimitatore, k )
   NEXT k
   WAIT
   RETURN
Il listato, molto semplice, visualizza a video una lista di tutte le singole parole presenti all'interno del file di testo "prova.txt".
Come si nota dal sorgente, i caratteri delimitatori, memorizzati nella stringa "cDelimitatore", hanno lo scopo di far conoscere, alle funzioni di parsing, la modalità con cui le stesse parole sono separate tra loro.
La funzione hb_MemoRead, molto potente, memorizza automaticamente, in una stringa, l'intero contenuto di un file di testo, specificato come parametro.
La successiva istruzione, che usa la funzione NumToken, assegna alla variabile nNumeroToken il numero dei token (parole) contenuti nella stringa.
All'interno del ciclo FOR-NEXT, universale in tutti i linguaggi, avviene la visualizzazione vera e propria, grazie alla funzione Token prima descritta. Essa avviene mostrando il numero progressivo e la relativa parola, prelevata dalla stringa.
Questo semplice programma serve a far capire bene il funzionamento base della procedura. Il lettore si soffermi a lungo su questa fase prima di proseguire con i successivi esempi, ben più complessi.

Classifica delle parole

Una statistica interessante sui testi, che normalmente non viene effettuata con i normali editor, è quella di ottenere una "classifica" delle parole più utilizzate nel proprio documento, al fine di conoscere la distribuzione delle stesse e di determinare un tasso statistico delle ripetizioni.
Il file di testo esaminato per questa prova è la famosa favola "Le avventure di Pinocchio" (in TXT). Si tratta di un grosso documento testuale, dal peso di circa 300 kB e formato da circa 40.000 parole. Dopo alcuni secondi di elaborazione, il programma "sforna" i primi responsi, decretando l'elenco delle parole più usate ed utilizzate nella favola.
Come è possibile osservare dalle illustrazioni, tralasciando gli articoli, le congiunzioni e le altre brevi particelle che, inevitabilmente, risultano gli elementi più numerosi, le parole più utilizzate nel romanzo sono "Pinocchio", con 414 occorrenze, seguita dalla parola "come" con 215 occorrenze, "disse" con 202 occorrenze e "burattino" con 195 occorrenze. Seguono altre parole, locuzioni ed altro.

Classifica delle parole nella Favola di Pinocchio

Pinocchio 414
disse     202
burattino 195
della     121
perché    108
aveva      95
sempre     94
povero     93
.......    ..
.......    ..
Geppetto   72
.......    ..
.......    ..
legno      64
REQUEST HB_CODEPAGE_ITISO
PROCEDURE Main()
   LOCAL cStringa, k, cDelimitatore, nNumeroToken
   cls
   hb_SetCodepage( "ITISO" )
   cDelimitatore := Chr( 0 ) + Chr( 9 ) + Chr( 10 ) + Chr( 13 ) + ;
      Chr( 26 ) + hb_BChar( 138 ) + hb_BChar( 141 ) + ;
      Chr( 32 ) + ",.;:!\?/\\<>()#&%+-*" + ;
      Chr( 34 ) + Chr( 39 )
   // ----Legge il file di testo-------
   cStringa := hb_MemoRead( "prova.txt" )
   // ----Conta le parole------
   nNumeroToken := NumToken( cStringa, cDelimitatore )
   // ------Crea il database su disco e inserisce le parole--------
   dbCreate( "prova.dbf", { { "parola", "c", 20, 0 }, { "quantita", "n", 5, 0 } } )
   USE prova
   FOR k = 1 TO nNumeroToken
      @ 2, 10 SAY AllTrim( Str( k ) ) + "/" + AllTrim( Str( nNumeroToken ) )
      APPEND BLANK
      REPLACE parola WITH Token( cStringa, cDelimitatore, k ), quantita WITH 1
   NEXT k
   // -----Totalizza le parole e mette in classifica----
   INDEX ON field->parola TO temp
   TOTAL ON field->parola fields quantita TO temp
   USE temp
   INDEX ON field->quantita TO temp DESCENDING
   Browse()
   USE
   RETURN
Anche questo software è molto semplice e segue la stessa filosofia del precedente. Al fine di memorizzare le parole su disco, sono presenti alcune funzioni che creano ed organizzano il database per ospitare, ordinare e totalizzare i risultati. Il sorgente è autoesplicativo ed i programmatori di computer non dovrebbero trovare difficoltà nel comprenderlo.
Se l'utente non dovesse avere la necessità di conteggiare le parole con un numero di caratteri inferiori a cinque (per esempio), può implementare una condizione di filtro per rifiutare proprio questa classe di elementi.

Coniugazioni dei verbi

Un'altra interessante applicazione statistica, sempre eseguita sulla favola di Pinocchio, è quella di conteggiare il numero dei verbi (all'infinito) appartenenti alle tre coniugazioni. Il programma fornisce, dopo l'elaborazione, la lista dei verbi, in ordine di "distribuzione".
Il calcolo avviene conteggiando singolarmente le parole in cui gli ultimi caratteri siano "ARE", "ERE" ed "IRE".
REQUEST HB_CODEPAGE_ITISO
PROCEDURE Main()
   LOCAL cStringa, k, cDelimitatore, nNumeroToken, cParola, cConiug
   cls
   hb_SetCodepage( "ITISO" )
   cDelimitatore := Chr( 0 ) + Chr( 9 ) + Chr( 10 ) + Chr( 13 ) + ;
      Chr( 26 ) + hb_BChar( 138 ) + hb_BChar( 141 ) + ;
      Chr( 32 ) + ",.;:!\?/\\<>()#&%+-*" + ;
      Chr( 34 ) + Chr( 39 )
   // ----Legge il file di testo-------
   cStringa := hb_MemoRead( "prova.txt" )
   // ----Conta le parole------
   nNumeroToken := NumToken( cStringa, cDelimitatore )
   // ------Crea il database su disco e inserisce le parole--------
   dbCreate( "prova.dbf", { { "parola", "c", 20, 0 }, { "quantita", "n", 5, 0 } } )
   USE prova
   FOR k = 1 TO nNumeroToken
      cParola := Token( cStringa, cDelimitatore, k )
      cConiug := Upper( Right( cParola, 3 ) )
      @ 2, 10 SAY AllTrim( Str( k ) ) + "/" + AllTrim( Str( nNumeroToken ) )
      IF cConiug = "ARE" .OR. cConiug = "ERE" .OR. cConiug = "IRE"
         APPEND BLANK
         REPLACE parola WITH cParola, quantita WITH 1
      ENDIF
   NEXT k
   // -----Totalizza le parole e mette in classifica----
   INDEX ON field->parola TO temp
   TOTAL ON field->parola fields quantita TO temp
   USE temp
   INDEX ON field->quantita TO temp DESCENDING
   Browse()
   USE
   RETURN

Elenco dei verbi infiniti più presenti

fare     65
essere   48
dire     43
mare     41
andare   36
vedere   25
.......  ..
.......  ..
.......  ..
Seguendo l'esempio è utile, anche, calcolare la quantità delle coniugazioni "ARE", "ERE" e "IRE" nel testo (nella fattispecie la favola di Pinocchio). Una semplice modifica al programma permette di eseguire il calcolo, decretando i seguenti risultati:
Coniugazione ARE: 602 (52%)
Coniugazione ERE: 409 (35%)
Coniugazione IRE: 154 (13%)

Ricerca delle parole ripetute vicine nel testo

Scrivendo articoli, capita sovente, inconsciamente e senza accorgersene, di ripetere la stessa parola in uno stesso paragrafo. Un controllo del testo, di solito, permette di scovare l'errore ma spesso tale duplicazione passa inosservata, con il risultato che il refuso viene scoperto dal lettore. Una frase di esempio potrebbe essere la seguente:
"Il transistor è un componente elettronico utilizzato in ogni recente progetto elettronico."
Come si vede, la parola "elettronico" è presente due volte nello stesso paragrafo dando luogo ad una sorta di "ripetizione" inutile, dannosa, fastidiosa ed indesiderata. Le due parole sono separate da altre cinque, per cui la loro relativa "vicinanza" è elevata. Con un parsing ad hoc del testo è possibile scovare questa tipologia di imperfezione che, vi assicuro, non è molto rara. Naturalmente alcune particelle, congiunzioni, articoli e altre brevi locuzioni (solitamente di una, due o tre caratteri) possono trovarsi tra loro a breve distanza. Questi elementi devono esistere nel testo e non possono essere considerati errori. Il controllo deve essere, quindi, parametrizzabile e personalizzabile. In particolare, le opzioni che l'utente potrebbe impostare sono:
  • la distanza massima entro cui l'errore di vicinanza deve essere considerato tale;
  • la lunghezza minima delle parole da esaminare.
Si effettui il test del software sul seguente testo di prova ("prova.txt") per poi commentare, in dettaglio, il listato:
"Il transistor è un componente elettronico spesso usato in un circuito elettronico. Il suo funzionamento, assimilabile al rubinetto, permette di simulare il funzionamento di un interruttore. Esistono diversi tipi di transistor e diversi tipi di configurazioni."
Il testo in questione deve essere memorizzato in un file testuale. Il programma prevede il nome di "prova.txt" ma l'utente può, ovviamente, assegnare un identificativo diverso.
Come si nota, il brano presenta alcune parole vicine ripetute due volte:
  • elettronico
  • funzionamento
  • diversi
  • tipi
Errori di questo tipo sono molto comuni durante la stesura e la digitazione di un articolo ed anche rileggendolo più volte per la correzione, sovente la svista passa inosservata. La distanza "soglia" tra le parole da controllare può essere regolata a piacere. Solitamente un valore compreso tra 6 e 10 può andar senz'altro bene.
REQUEST HB_CODEPAGE_ITISO
PROCEDURE Main()
   LOCAL cStringa, cDelimitatore, nNumeroToken,  nDistanzaMax
   LOCAL cParola1, cParola2
   LOCAL nPos1, nPos2, nCount
   LOCAL i, j
   cls
   hb_SetCodepage( "ITISO" )
   cDelimitatore := Chr( 0 ) + Chr( 9 ) + Chr( 10 ) + Chr( 13 ) + ;
      Chr( 26 ) + hb_BChar( 138 ) + hb_BChar( 141 ) + ;
      Chr( 32 ) + ",.;:!\?/\\<>()#&%+-*" + ;
      Chr( 34 ) + Chr( 39 )
   // ----Legge il file di testo-------
   cStringa := hb_MemoRead( "prova.txt" )
   // ----Conta le parole------
   nNumeroToken := NumToken( cStringa, cDelimitatore )
   nDistanzaMax := 8
   FOR i = 1 TO nNumeroToken - 1
      FOR j = i + 1 TO i + nDistanzaMax
         // ----Confronta le due parole-----------
         cParola1 := Token( cStringa, cDelimitatore, i )
         cParola2 := Token( cStringa, cDelimitatore, j )
         IF Upper( cParola1 ) == Upper( cParola2 ) .AND. Len( cParola1 ) > 3
            nPos1 := AtToken( cStringa, cDelimitatore, i )
            nPos2 := AtToken( cStringa, cDelimitatore, j )
            nCount := nPos2 - nPos1 + Len( cParola1 ) + 20
            SET COLOR TO rg +/ n
            ? "Parola Ripetuta:   "
            SET COLOR TO w +/ n
            ?? cParola1
            SET COLOR TO g +/ n
            ? "Frase contenitore: "
            SET COLOR TO r +/ n
            ?? SubStr( cStringa, nPos1, nCount  )
            SET COLOR TO w / n
            ? Replicate( "-", 70 )
         ENDIF
      NEXT j
   NEXT i
   SET COLOR TO
   RETURN
La filosofia dell'algoritmo utilizzato è la seguente: si confronta ogni parola con le successive, rispettando la tolleranza e la soglia impostata. Se le due parole sottoposte a raffronto sono uguali, l'intera frase viene visualizzata a video. In questo modo l'utente, rintracciandola sul documento originale, può apportare le dovute modifiche.
Dopo queste prove l'utente si sarà fatta un'idea generale di quelle che potrebbero essere le potenzialità di una tale gestione.
Le possibilità di controllo, di elaborazione e di statistica sono infinite e la personalizzazione può avvenire, praticamente, per qualsiasi esigenza.
Alcuni esempi che potrebbero essere implementati, sia a livello di controllo errori che di statistica sintattica, sono i seguenti:
  • controllo di grammatica (verbo essere e avere);
  • conteggio dei nomi propri (iniziale maiuscola);
  • conteggio verbi al futuro;
  • controllo stessa vocale finale di una parola e iniziale della successiva;
  • statistiche di conteggio varie;
  • e molti altri........
Il lettore saprà trovare, sicuramente, applicazioni a lui più congeniali, che consentano di risolvere qualsiasi tipologia di esigenza.

Conclusioni

Come si è visto, l'articolo fornisce uno spunto per delle applicazioni di controllo che possono portare a grandi risultati. Un'ulteriore idea potrebbe essere quella di aggiungere un dizionario in un database (solitamente di tipo DBF) ed eseguire il controllo parola per parola.
Se si vuole una maggiore elasticità nel programma, specialmente nella trattazione nei testi molto pesanti, il programmatore può, preventivamente, inserire l'insieme delle parole in un array dinamico (normale o associativo) al fine di gestire l'insieme dei token con più potenza e velocità.
Qualsiasi sia la fonte dell'articolo scritto, l'utente deve sempre convertirlo in formato TXT per darlo in pasto al programma di parsing.
Il realizzare un controllo personalizzato sui testi costituisce un prezioso aiuto che, in concerto con altri parser esistenti, contribuisce ad aumentare la correttezza e l'efficacia dei propri scritti.
GDM

 

Quello che hai appena letto è un Articolo Premium reso disponibile affinché potessi valutare la qualità dei nostri contenuti!

 

Gli Articoli Tecnici Premium sono infatti riservati agli abbonati e vengono raccolti mensilmente nella nostra rivista digitale EOS-Book in PDF, ePub e mobi.
volantino eos-book1
Vorresti accedere a tutti gli altri Articoli Premium e fare il download degli EOS-Book? Allora valuta la possibilità di sottoscrivere un abbonamento a partire da € 2,95!
Scopri di più

Leave a Reply

Flip&Click Arduino e Python compatibile

Fai un abbonamento Platinum (EOS-Book + Firmware), ricevi in OMAGGIO la nuova Flip&Click, inviaci il tuo progetto e OTTIENI IL RIMBORSO