lunedì 19 gennaio 2015

Creare un programma DIFF (parte 2)

Nel post precedente abbiamo visto come trovare correttamente le differenze fra le righe di due testi, ma quali applicazione ha?
Innanzitutto premettiamo che il metodo visto si può applicare alle righe di un testo, come alle parole, alle lettere, ai puntini che compongono un'immagine, a qualsiasi cosa.
I siti come Dropbox, Google, Mega, offrono di ospitare i vostri file e, ogni volta che aggiornate un file, di mantenere le versioni precedenti. Per risparmiare spazio, invece di avere più copie di file con piccole differenza, mantengono nei loro archivi solo un file e le successive differenze.
Anche nei formati video digitali, ogni 5 fotogrammi, abbiamo i dati di un fotogramma intero e solo le differenze per i successivi 4 fotogrammi; sempre per ridurre spazio.
Trovare le differenza ci serve anche in biologia e medicina, per capire cosa è effettivamente cambiato in un DNA, da una specie ad un'altra, o una lunga molecola organica come si è modificata.
Nel campo dell'informatica, si possono mandare solo i DIFF di un programma, e i programmi di patch, applicheranno solo le modifiche; in questo modo non c'è bisogno di inviare tutto il programma.
Ora vediamo di creare un programma che ci faccia vedere le differenze fra sue testi, molto usato in informatica.
Ci basterà sfruttare il codice mostrato nell'altro post, aggiungendo il solo il modo di colorare il testo.
Otterremo un programma simile a questo:

Nelle due colonne in basso a destra abbiamo i due testi affiancati con le righe eliminate in rosso e quelle aggiunte in verde. Chiamando questi due campi rispettivamente: VersAdiff e VersBdiff, il codice per ottenere il testo colorato è il seguente:

on mouseUp
   #trasformiamo i testi in array, dove ogni riga è un elemento dell'array   
   put the text field "versA" into tOldSource
   put the text field "versB" into tNewSource
   split tOldSource using return
   split tNewSource using return
   #cerchiamo di capire di quanti elementi, cioè righe, è fatto ogni array
   #extents mostra una serie di righe con due numeri, il numero minimo e il massimo per ogni dimensione dell'array
   #qui la dimensione è 1
   put item 2 of the extents of tOldSource into tOldLineCount
   put item 2 of the extents of tNewSource into tNewLineCount
   #calcoliamo le varie sequenze con il massimo testo in comune (LCSL)   
   put CalculateLCSLengths(tOldSource, tNewSource, tOldLineCount, tNewLineCount) into LCLS
   #e adesso calcoliamo il DIFF      
   lock screen
   put empty into field "VersAdiff"
   put empty into field "VersBdiff"   
   DetermineDiff LCLS, tOldSource, tNewSource, tOldLineCount, tNewLineCount
   DetermineDiff2 LCLS, tOldSource, tNewSource, tOldLineCount, tNewLineCount
   unlock screen
end mouseUp

function CalculateLCSLengths tOldSource, tNewSource, tOldLineCount, tNewLineCount   
   #costruiamo la tabella per il calcolo LCSL
   #la riga 0 è tutti 0
   repeat with tRow = 0 to tOldLineCount
      put 0 into pLCSLength[tRow,0]
   end repeat
   #la colonna 0 è tutti 0
   repeat with tCol = 0 to tNewLineCount
      put 0 into pLCSLength[0,tCol]
   end repeat
   #ora calcoliamo le altre varie celle della matrice LCLS
   repeat with tRow = 1 to tOldLineCount
      repeat with tCol = 1 to tNewLineCount
         put tOldSource[tRow] into tOldLine
         put tNewSource[tCol] into tNewLine
         if tOldLine = tNewLine then
            put pLCSLength[tRow - 1,tCol - 1] + 1 into pLCSLength[tRow,tCol]
         else
            put max(pLCSLength[tRow - 1,tCol],pLCSLength[tRow,tCol - 1]) into pLCSLength[tRow,tCol]
         end if
      end repeat
   end repeat
   #finito
   return pLCSLength
end CalculateLCSLengths


#abbiamo un messaggio che chiama se stesso parecchie volte
#qui usiamo le @ per risparmiare meomoria ed evitare troppe copie della stessa variabile, però stiamo attenti a non modificarle!
on DetermineDiff @pLCSLength, @pOldSource, @pNewSource, pRow, pCol
   put pOldSource[pRow] into tOldLine #la riga nel file originale
   put pNewSource[pCol] into tNewLine #la riga nella nuova versione
   if pRow > 0 and pCol > 0 and tOldLine = tNewLine then
      #sono uguali, andiamo alla cella in alto a sinistra rispetto all'attuale nel LCLS            
      DetermineDiff pLCSLength, pOldSource, pNewSource, pRow - 1, pCol - 1
      put the htmltext of field versAdiff into temp
      set the htmltext of field VersAdiff to ( temp & prow & "	" & tOldLine )
   else if pCol > 0 and (pRow = 0 or pLCSLength[pRow,pCol - 1]   >= pLCSLength[pRow - 1, pCol]) then
      ##caso la cella a sinistra è maggiore o uguale di quella sopra
      #è stata aggiunta una riga, moviamoci a sinistra nella tabella LCLS         
      DetermineDiff pLCSLength, pOldSource, pNewSource, pRow,   pCol - 1
      #set the text of field uscita to (field uscita & prow& "+" & tab & tNewLine & return )
   else if pRow > 0 and (pCol = 0 or pLCSLength[pRow,pCol - 1] < pLCSLength[pRow - 1, pCol]) then
      ##caso la cella a sinistra è minore di quella sopra
      #è stata rimossa una riga, saliamo di una riga nella tabella LCLS         
      DetermineDiff pLCSLength, pOldSource, pNewSource, pRow - 1, pCol
      put the htmltext of field VersAdiff into temp
      set the htmltext of field VersAdiff to (temp &"<font bgcolor=red>" & prow & "&#9;" & tOldLine & "</font>" )
   end if
end DetermineDiff

#abbiamo un messaggio che chiama se stesso parecchie volte
#qui usiamo le @ per risparmiare meomoria ed evitare troppe copie della stessa variabile, però stiamo attenti a non modificarle!
on DetermineDiff2 @pLCSLength, @pOldSource, @pNewSource, pRow, pCol
   put pOldSource[pRow] into tOldLine #la riga nel file originale
   put pNewSource[pCol] into tNewLine #la riga nella nuova versione
   if pRow > 0 and pCol > 0 and tOldLine = tNewLine then
      #sono uguali, andiamo alla cella in alto a sinistra rispetto all'attuale nel LCLS            
      DetermineDiff2 pLCSLength, pOldSource, pNewSource, pRow - 1, pCol - 1
      put the htmltext of field versBdiff into temp
      set the htmltext of field VersBdiff to ( temp & prow & "&#9;" & tOldLine )
   else if pCol > 0 and (pRow = 0 or pLCSLength[pRow,pCol - 1]   >= pLCSLength[pRow - 1, pCol]) then
      ##caso la cella a sinistra è maggiore o uguale di quella sopra
      #è stata aggiunta una riga, moviamoci a sinistra nella tabella LCLS         
      DetermineDiff2 pLCSLength, pOldSource, pNewSource, pRow,   pCol - 1
      put the htmltext of field VersBdiff into temp
      set the htmltext of field VersBdiff to (temp & "<font bgcolor=green>" & prow & "&#9;" & tNewLine & "</font>")      
   else if pRow > 0 and (pCol = 0 or pLCSLength[pRow,pCol - 1] < pLCSLength[pRow - 1, pCol]) then
      ##caso la cella a sinistra è minore di quella sopra
      #è stata rimossa una riga, saliamo di una riga nella tabella LCLS         
      DetermineDiff2 pLCSLength, pOldSource, pNewSource, pRow - 1, pCol   
   end if
end DetermineDiff2


Possiamo renderlo graficamente più professionale facendo in modo che muovendo un solo scroller, tutte i due testi scorrano contemporaneamente, mostrando sempre proporzionalmente la stesse parti. Ci basterà levare lo scroll verticale dal campo VersAdiff, e mettere nel secondo campo (VersBdiff) il seguente codice:

on scrollbardrag tdrag
   put the formattedheight of me into myh
   put the formattedheight of field versAdiff into ah
   put tdrag / myh into perc
   put round(perc * ah) into ah2
   set the vscroll of field "VersADiff" to ah2
end scrollbardrag

arrivati a questo punto, affiancandoli bene avremo il risultato voluto.
Come vedete si riesce a vedere bene cosa è stato aggiunto e cosa è stato levato.
Il programma è scaricabile da qui: diff.livecode
Questo tipo di analisi, tipica dell'informatica, è applicabile a qualunque campo. Voi dove l'applichereste?