Utente:Alex brollo/Match and Fix

Da Wikisource.
Jump to navigation Jump to search

Obiettivo: dati due testi a e b, identificare, nelle sequenze simili, singole parole discordanti e sostituire le parole discordanti di a con le corrispondenti di b.

Tecnica: lavorare su liste ottenute con le regex di Match e successivo oggetto difflib.

Ottenuto l'oggetto match vanno distinte:

  1. sequenze allineate;
  2. sequenze totalmente disallineate, "lunghe";
  3. sequenze dilallineate brevi (iniziare con le sequenze disallineate di singole parole).

Funzioni:

  1. pre-elaborazione testi (riunione hyphen): dehyphen(testo); output: testo con hyphen rimossi (tranne l'eventuale hyphen a fine testo)
  2. split via regex: split(testo); output: l1 e l2 (lista delle sole parole, lista delle parole e delle "non parole")
  3. ricostruzione dalle sequenze split rebuild(l1, l2, verbose=False); output: testo ricostruito interpolando in l2 la lista l1, eventualmente modificata
  4. matching con ricerca differenze in parole singole o in piccoli gruppi di parole di lunghezza uguale: match(testo1,testo2); output: [a,b,c] dove a=split(testo1), b=split(testo2), c=lista blocchi discordanti)
  5. correzione di testo1 sulla base di testo2: output(a,b,c) (a,b,c è l'output di match(); restituisce la lista l1 modificata, da cuoi può essere ricostruito testo1 corretto

Script base[modifica]

import re
import difflib
# riunione parole spezzate a fine riga
def dehyphen(testo):
    testo=testo.strip().split("\n")
    for i in range(len(testo)):
        testo[i]=testo[i].rstrip()
    testo="\n".join(testo)
    testo=testo.replace("-\n","").replace(u\n","")
    return testo

# splitting di testo in due liste: parole; parole-non parole
def split(testo):
    p = re.compile(ur'[\W]+', re.U) # lista sequenze caratteri
    fp = re.compile(ur'([\W]+)', re.U) # lista sequenze caratteri e non caratteri
    
    testo_p=p.split(testo)
    testo_fp=fp.split(testo)
    
    return [testo_p, testo_fp]

# ricostruzione del testo dalle liste testo_p (con eventuali elementi modificati), testo_fp
def rebuild(l1,l2,verbose=False):
    if l1[0]=="": # caso testo inizia con non-parola
        r=range(1,len(l1))
    else:
        r=range(len(l1))
    for i in r:
        if verbose:
            print l1[i],l2[i*2]
        l2[i*2]=l1[i]
    testo="".join(l2)
    return testo


# restituisce testo1 e testo2 splittatti e la lista degli unmatching blocks
def match(testo1,testo2):
    s = difflib.SequenceMatcher()
    t1=dehyphen(testo1).strip()
    t2=dehyphen(testo2)
    t1s=split(t1)
    t2s=split(t2)
    s.set_seqs(t1s[0],t2s[0])
    mb=s.get_matching_blocks()
    len_t1s=mb[len(mb)-1][0]
    offset=0
    unmatching_blocks=[]
    mbi=0 # indice degli elementi di mb
    ao=bo=0 # offset iniziale di a e b

    while True:
        al=mb[mbi][0]-ao
        bl=mb[mbi][1]-bo
        if al>0 and bl>0:
            unmatching_blocks.append([ao, al, bo, bl])
        ao=mb[mbi][0]+mb[mbi][2]
        bo=mb[mbi][1]+mb[mbi][2]
        mbi+=1
        if mbi>len(mb)-1:
            print "fine"
            break
    
    return (t1s,t2s,unmatching_blocks)

# riceve a, b, um dalla funzione match; filtra gli elementi um scegliendo quelli "semplici",
# per ora di lunghezza uguale; restituisce il testo corretto e ricostruito
def output(a,b,um):
    for blocco in um:
        #selezione elementi unmatching "semplici"
        if blocco[1] != blocco[3]:
            continue
        print a[0][blocco[0]:blocco[0]+blocco[1]],b[0][blocco[2]:blocco[2]+blocco[3]]
        for i in range(blocco[1]):
            a[0][blocco[0]+i]=b[0][blocco[2]+i]

    return rebuild(a[0],a[1])

Primo test di fattibilità[modifica]

  • testo1: OCR della prima pagina della parte I di Tiraboschi, ripulita dall'OCR della nota laterale
  • testo2: l'intero txt del I file LiberLiber, estratto dal file ODT (corposo txt di oltre 500 kby)
  • lancio di match(testo1,testo2) senza alcun tentativo di "appaiare" i testi per pagine
  • tempo di risposta: meno di 1 secondo!
  • parole identificate come errate (con outpuit()):
[u'necessanamente'] [u'necessariamente']
[u'il'] [u'i']
[u'ilalia'] [u'Italia']
[u'1'] [u'l']
[u'eh'] [u'di']
  • evviva. :-)
  • Nota: per "ripulire" l'OCR FineReader dalle note laterali impropriamente "appiccicate" alle linee di testo occorre manipolare le aree di riconoscimento OCR

Secondo test[modifica]

Modificato output per finire la procedura di correzione, adesso si tratta solo di creare un loop che invii a output una sequenza di pagine e carichi il risultato in nsPagina o in alternativa, per chi non ha un bot, assembli un testo con codici split.

Parametri: nome file djvu, pagina iniziale, pagina finale, nome file testo di riferimento.