Progetto:Bot/Programmi in Python per i bot/matchAndFix.py

# -*- coding: utf-8 -*-
# text alignment program
# derived from align.py
# licence: GPL
# version: 3

import os, re
import difflib

import subprocess
import pickle

import pywikibot as bot
it=bot.Site("it","wikisource")
# tiraboschi

djvuI="tirab/djvu/Tiraboschi - Storia della letteratura italiana, Tomo I, Classici italiani, 1822, I.djvu"
djvuII="tirab/djvu/Tiraboschi - Storia della letteratura italiana, Tomo II, Classici italiani, 1823, II.djvu"
djvuIII="tirab/djvu/Tiraboschi_-_Storia_della_letteratura_italiana,_Tomo_III,_Classici_italiani,_1823,_III.djvu"
djvuIV="tirab/djvu/Tiraboschi_-_Storia_della_letteratura_italiana,_Tomo_IV,_Classici_italiani,_1823,_IV.djvu"
djvuV="tirab/djvu/Tiraboschi - Storia della letteratura italiana, Tomo V, parte 1, Classici Italiani, 1823, V.djvu"
djvuVI="tirab/djvu/Tiraboschi_-_Storia_della_letteratura_italiana,_Tomo_V,_parte_2,_Classici_italiani,_1823,_VI.djvu"
djvuVII="tirab/djvu/Tiraboschi_-_Storia_della_letteratura_italiana,_Tomo_VI,_parte_1,_Classici_italiani,_1824,_VII.djvu"
djvuVIII="tirab/djvu/Tiraboschi_-_Storia_della_letteratura_italiana,_Tomo_VI,_parte_2,_Classici_italiani,_1824,_VIII.djvu"
djvuX="tirab/djvu/Tiraboschi_-_Storia_della_letteratura_italiana,_Tomo_VII,_parte_1,_Classici_italiani,_1824,_X.djvu"
djvuXI="tirab/djvu/Tiraboschi_-_Storia_della_letteratura_italiana,_Tomo_VII,_parte_2,_Classici_italiani,_1824,_XI.djvu"
djvuXII="tirab/djvu/Tiraboschi_-_Storia_della_letteratura_italiana,_Tomo_VII,_parte_3,_Classici_italiani,_1824,_XII.djvu"
djvuXIII="tirab/djvu/Tiraboschi_-_Storia_della_letteratura_italiana,_Tomo_VII,_parte_4,_Classici_italiani,_1824,_XIII.djvu"
djvuIX="tirab/djvu/Tiraboschi - Storia della letteratura italiana, Tomo VI, parte 3, Classici italiani, 1824, IX.djvu"
djvuV="tirab/djvu/Tiraboschi - Storia della letteratura italiana, Tomo V, parte 1, Classici Italiani, 1823, V.djvu"
djvuXVI="tirab/djvu/Tiraboschi_-_Storia_della_letteratura_italiana,_Tomo_IX,_Indici,_Classici_italiani,_1826,_XVI.djvu"
djvuXIV="tirab/djvu/Tiraboschi_-_Storia_della_letteratura_italiana,_Tomo_VIII,_parte_1,_Classici_italiani,_1824,_XIV.djvu"
djvuXV="tirab/djvu/Tiraboschi_-_Storia_della_letteratura_italiana,_Tomo_VIII,_parte_2,_Classici_italiani,_1824,_XV.djvu"
txtXVI="tirab/txt/Volume_XVI.txt"
txtI="tirab/txt/tomo_1.txt"
txtII="tirab/txt/tomo_2.txt"
txtIII="tirab/txt/tomo_3.txt"
txtIV="tirab/txt/Volume_IV.txt"
txtIII="tirab/txt/tomo_3.txt"
txtX="tirab/txt/Volume_10.txt"
txtIX="tirab/txt/Volume_IX.txt"
txtXIII="tirab/txt/Volume_XIII.txt"
txtXII="tirab/txt/Volume_XII.txt"
txtXI="tirab/txt/Volume_XI.txt"
txtV="tirab/txt/tomo_V.txt"
txtVI="tirab/txt/tomo_VI.txt"
txtVIII="tirab/txt/Tomo_VIII.txt"




# Nuova versione, gestisce i tag annidati; x e' la parte "aspecifica" del
# tag di apertura (es: {{ cercando {{Intestazione| )
def find_stringa(stringa,idi,idf,dc=0,x=None,side="left"):
    if side=="right":
        idip=stringa.rfind(idi)
    else:
        idip=stringa.find(idi)
    idfp=stringa.find(idf,idip+len(idi))+len(idf)
    if idip>-1 and idfp>0:
        if x!=None:
            while stringa[idip:idfp].count(x)>stringa[idip:idfp].count(idf):
                if stringa[idip:idfp].count(x)>stringa[idip:idfp].count(idf):
                    idfp=stringa.find(idf,idfp)+len(idf)
                
        if dc==0:
            vvalore=stringa[idip+len(idi):idfp-len(idf)]
        else:
            vvalore=stringa[idip:idfp]
    else:
        vvalore=""
    return vvalore

def produci_lista(testo,idi,idf,dc=1,inizio=None):
    t=testo[:]
    lista=[]
    while not find_stringa(t,idi,idf,1,inizio)=="":
        el=find_stringa(t,idi,idf,1,inizio)
        t=t.replace(el,"",1)
        if dc==0:
            el=find_stringa(el,idi,idf,0,inizio)
        lista.append(el)
    return lista


def parseTemplate(template):
    elementi={}
    n=1
    template=template.strip()[2:-2]
    #codifica link
    x=produci_lista(template,"[[","]]",1)
    for i in x:
        key="##%d##" % (n)
        elementi[key]=i
        template=template.replace(i,key)
        n+=1
    #codifica template
    x=produci_lista(template,"{{","}}",1,"{{")
    
    for i in x:
        key="##%d##" % (n)
        elementi[key]=i
        template=template.replace(i,key)
        n+=1

    #codifica url
    x=produci_lista(template,"[","]",1)
    
    for i in x:
        key="##%d##" % (n)
        elementi[key]=i
        template=template.replace(i,key)
        n+=1
    template=template.split("|")
    #normalizzazione parametri
    n=1
    for i in range(1,len(template)):
        if not "=" in template[i]:
            template[i]=str(n)+" = "+template[i]
            n+=1
    
    
        template[i]=template[i].strip()
        for j in elementi:
            template[i]=template[i].replace(j,elementi[j])
    templateDict={}
    templateDict["nome"]=template[0].strip()
    r=re.compile(" *= *")
    for i in range(1,len(template)):
        tv=r.split(template[i])
        templateDict[tv[0].strip()]=tv[1].strip()

    return templateDict


# elimina l'escaping dei caratteri speciali nel djvu text
def unquote_text_from_djvu(text):
    #text = text.replace(u'\\r', u'\r')
    text = text.replace(u'\\n', u'\n')
    text = text.replace(u'\\"', u'"')
    text = text.replace(u'\\\\', u'\\')
    text = text.replace(u'\\037', u'\n')
    text = text.replace(u'\\035', u'')
    text = text.replace(u'\\013', u'')
    text = text.replace(u'\\t', u' ')
    text = text.rstrip(u'\n')
    return text
# estrae lo strato testo in datail page, e produce la lista data dei testi 
# delle pagine (pagina 1 in data[0]);
# il file djvu deve essere scaricato nella cartella locale
def extract_djvu_text(filename):
    print "extracting text layer"

    if type(filename) == type(u''):
        filename = filename.encode('utf-8')

    #utils.copy_file_from_url(url, filename, sha1)

    data = []
    # GTK app are very touchy
    os.environ['LANG'] = 'en_US.UTF8'

    comando='djvutxt --detail=page "%s"  text.txt' % (unicode(filename,"utf-8").encode("latin-1"))
    result=os.system(comando)
    print comando, result
    text=open("text.txt").read()
    for t in re.finditer(u'\((page -?\d+ -?\d+ -?\d+ -?\d+[ \n]+"(.*)"[ ]*|)\)\n', text):
        t = unicode(t.group(1), 'utf-8', 'replace')
        t = re.sub(u'^page \d+ \d+ \d+ \d+[ \n]+"', u'', t)
        t = re.sub(u'"[ ]*$', u'', t)
        t = unquote_text_from_djvu(t)
        # proviamo ad aggirare il bug logico
        if len(t)<150:
            t=""
        data.append(t)

    # os.remove(filename)

    return data

def extract_pdf_text(filename):
    print "extracting text layer from pdf"

    if type(filename) == type(u''):
        filename = filename.encode('utf-8')

    #utils.copy_file_from_url(url, filename, sha1)

    data = []
    # GTK app are very touchy
    os.environ['LANG'] = 'en_US.UTF8'

    comando='pdftotext -enc UTF-8 "%s"  text.txt' % (filename)
    result=os.system(comando)
    print comando, result
    text=open("text.txt").read()
    text=unicode(text,"utf-8")
    text=text.replace(u"¬ ","")
    data=text.split(u"\x0c")

    # os.remove(filename)

    return data

# restituisce e stampa un messaggio di errore
def ret_val(error, text):
    if error:
        print "Error: %d, %s" % (error, text)
    return  { 'error' : error, 'text' : text }

E_ERROR = 1
E_OK = 0

	
# It's possible to get a name collision if two different wiki have local
# file with the same name but different contents. In this case the cache will
# be ineffective but no wrong data can be used as we check its sha1.
def get_djvu(cache, mysite, djvuname, check_timestamp = False):

    print "get_djvu", repr(djvuname)

    djvuname = djvuname.replace(" ", "_")
    cache_filename = djvuname + '.dat'

    obj = cache.get(cache_filename)
    if not obj:
        print "CACHE MISS"
        filepage = copy_File.get_filepage(mysite, djvuname)
        if not filepage:
            # can occur if File: has been deleted
            return None
        try:
            url = filepage.fileUrl()
            obj = extract_djvu_text(url, djvuname, filepage.getFileSHA1Sum())
        except:
            utils.print_traceback("extract_djvu_text() fail")
            obj = None
        if obj:
            cache.set(cache_filename, obj)
        else:
            return None
    else:
        if check_timestamp:
            filepage = copy_File.get_filepage(mysite, djvuname)
            if not filepage:
                # can occur if File: has been deleted
                return None
            sha1 = filepage.getFileSHA1Sum()
            if sha1 != obj[0]:
                print "OUTDATED FILE"
                url = filepage.fileUrl()
                try:
                    obj = extract_djvu_text(url, djvuname, sha1)
                    cache.set(cache_filename, obj)
                except:
                    return None

    return obj[1]

def carica_pcl(nome_file, folder="dati/"):
    nome_file=folder+nome_file+".pcl"
    f=open(nome_file)
    contenuto=pickle.load(f)
    f.close()
    return contenuto

def salva_pcl(variabile,nome_file="dato",folder="dati/"):
    nome_file=folder+nome_file+".pcl"
    f=open(nome_file,"w")
    pickle.dump(variabile, f)
    f.close()
    print "Variabile salvata nel file "+nome_file
    return 


    



# 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
    p=re.compile(ur"[;:., -()\n\t]+",re.U)
    fp=re.compile(ur"([;:., -()\n\t]+)",re.U)
    testo_p=p.split(testo)
    testo_fp=fp.split(testo)
    
    return [testo_p, testo_fp]

def pos(t1,t2,i):
    pos=i*2
    if t1[0]=="":
        pos+=1
    return pos

# 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,noDigits):
    for blocco in um:
        #selezione elementi unmatching "semplici"
        if blocco[1] != blocco[3]:
            if abs(blocco[1]-blocco[3])<3:
                print "**** vedi:",a[0][blocco[0]:blocco[0]+blocco[1]],b[0][blocco[2]:blocco[2]+blocco[3]]
                if blocco[1]==2 and blocco[3]==1:
                    offset=pos(a[0],a[1],blocco[0])
                    print "********", a[1][offset:offset+3]
                    
                    


            continue
        
        print a[0][blocco[0]:blocco[0]+blocco[1]],b[0][blocco[2]:blocco[2]+blocco[3]]
        if blocco[1]>2:
            lista_a=a[0][blocco[0]:blocco[0]+blocco[1]]
            lista_b=b[0][blocco[2]:blocco[2]+blocco[3]]
            
            if simil(" ".join(lista_a)," ".join(lista_b),2)<50 :
                if not simill(lista_a, lista_b):
                    conferma=raw_input("sequenza molto diversa, confermi match?")
                    if len(conferma)!=1 or (not conferma in ("sSyY")):
                        continue
        for i in range(blocco[1]):
            #esclusione differenze solo caso carattere
            # differenza solo in mM o probabile abbreviazione in b
            if a[0][blocco[0]+i].lower() != b[0][blocco[2]+i].lower():
                if len(a[0][blocco[0]+i])-len(b[0][blocco[2]+i]) > 2:
                    print "probabile abbreviatura per lunghezza"
                elif a[0][blocco[0]+i].startswith(b[0][blocco[2]+i]):
                    print "probabile abbreviatura per identità iniziale"
                else:
                    if noDigits and b[0][blocco[2]+i].isdigit():
                        print "numero, ignoro, correggo: ",a[0][blocco[0]+i],
                        a[0][blocco[0]+i]=a[0][blocco[0]+i].replace("i","1")\
                                           .replace("I","1")\
                                           .replace("l","1")\
                                           .replace("o","0")\
                                           .replace(u"°","0")
                        print " in ",a[0][blocco[0]+i]
                    else:
                        print "    ",a[0][blocco[0]+i],b[0][blocco[2]+i]
                        a[0][blocco[0]+i]=b[0][blocco[2]+i]
            
            else:
                print "differenza esclusiva maiuscole-minuscole"
                

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

def simil(a,b,c=4):
    punti=0
    try:
        for i in range(len(a)-c):
            if a[i:i+c]in b:
                punti+=1
        s1=punti*100/(len(a)-c)
        punti=0
        x=a
        a=b
        b=x
        for i in range(len(a)-c):
            if a[i:i+c]in b:
                punti+=1
        s2=punti*100/(len(a)-c)
        return int((s1+s2)/2)
    except:
        return -1

def simill(a,b,f=1):
    # a e b sono liste di stringhe di uguale lunghezza
    # f è un indice di somiglianza (1: max differenza media di lunghezza di 1 carattere per parola);
    # il valore 1 è molto conservativo, max suggerito 1.5
    delta=0
    for i in range(len(a)):
        delta+=abs(len(a[i])-len(b[i]))
    return delta<=len(a)*f
    
def cleanup(testo):
    sostNorm=u''' )	)
( 	(
 :	:
 ;	;
 .	.
 ?	?
 !	!
'\t’'''.split("\n")
    for i in range(len(sostNorm)):
        sostNorm[i]=sostNorm[i].split("\t")
    for s in sostNorm:
        testo=testo.replace(s[0],s[1])
    testo=re.sub(ur"([^aeiouAEIOU])([’']) ([aeiouAEIOU])",r"\1\2\3",testo)
    testo=re.sub(ur"([aeiouAEIOU])([’'])([^ \naeiouAEIOU])",r"\1\2 \3",testo)
    return testo
    


def maf(djvu=None, ini=1, fin=1000, txt=None,load=False, doMatch=True, exists=True, noDigits=False):
##    if load:
##        import pywikibot as bot
##        it=bot.Site("it","wikisource")
    testo=""
    if djvu==None:
        djvu="tirab/djvu/Tiraboschi - Storia della letteratura italiana, Tomo I, Classici italiani, 1822, I.djvu"
    if txt==None:
        txt="tirab/txt/prova1.txt"
    # estrazione testo (formato data)
    pagine=extract_djvu_text(djvu)
    if doMatch: # viene eseguito il match and fix
        t2=unicode(open(txt).read(),"utf-8")
        t2=cleanup(t2)
        for numeroPagina in range(ini,fin):
            basePagina=djvu
            if "/" in basePagina:
                basePagina=basePagina[basePagina.rfind("/")+1:]
            nomePagina="Pagina:"+basePagina+"/"+str(numeroPagina)
            if exists:
                pag=bot.Page(it,nomePagina)
                if pag.exists():
                    if "{{" in pag.get() or not 'level="1"' in pag.get():
                        print "Pagina ",pag.title(), "formattata, ignoro"
                        continue
            print "\n==[["+nomePagina+"]]==\n"
            t1=pagine[numeroPagina -1].replace("%","@")
            t1=cleanup(t1)
            t1s,t2s,um=match(t1,t2)
            testoCorretto=output(t1s,t2s,um,noDigits).replace("@","%")

            #print "==[["+nomePagina+"]]==\n"+testoCorretto
            testo+= "\n==[["+nomePagina+"]]==\n"+testoCorretto
            #print "\n==[["+nomePagina+"]]==\n"
        
            if load and exists:
                
                # disabilito blocco sovrascrittura
                #if (not pg.exists()) or ('level="1"' in pg.get() and not "{{" in pg.get()):
                #pg=bot.Page(it,nomePagina)
                pag.put(testoCorretto, "Testo ottenuto con Match and Fix 2")
    else: # viene eseguita la sola esportazione dell'OCR con codici Split
        basePagina=djvu
        testo=""
        if "/" in basePagina:
            basePagina=basePagina[basePagina.rfind("/")+1:]
        for numeroPagina in range(ini,fin):
            t1=pagine[numeroPagina -1]
            nomePagina="Pagina:"+basePagina+"/"+str(numeroPagina)
            testo+="\n==[["+nomePagina+"]]==\n"+t1
            
    open("tirab/output/testoSplit.txt","w").write(testo.encode("utf-8"))
    print
    print "Testo con  codici split salvato in tirab/output/testoSplit.txt"
    return 



                   
# funzione che produce l'ewlenco delle pagine con link Pg errati (con terzo parametro vuoto)
def cerca():
    base="Pagina:Tiraboschi - Storia della letteratura italiana, Tomo IX, Indici, Classici italiani, 1826, XVI.djvu/"
    for i in range(1, 416):
        testo=bot.Page(it, base+str(i)).get()
        e=find_stringa(testo, "{{Pg|","|}}",1,"{{")
        
        if e!="":
            print i,
    print
    return