sshoc-skosmapping/Progetto_Lett.ipynb

41 KiB

Test per Parsing e generazione IRI

In [2]:
import ast
import sys
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
# importing useful Python utility libraries we'll need
from collections import Counter, defaultdict
import itertools
In [3]:
import xml.etree.ElementTree as ET
In [4]:
#tree = ET.parse('/Users/cesare/Projects/hdn/triple/DanteTriple/xml/DanteSearch/grammaticale/inferno_forparsing.xml')
In [5]:
#root = tree.getroot()
In [6]:
from bs4 import BeautifulSoup
In [7]:
def read_tei(tei_file):
    with open(tei_file, 'r') as tei:
        soup = BeautifulSoup(tei, 'lxml')
        return soup
    raise RuntimeError('Cannot generate a soup from the input')
In [8]:
def elem_to_text(elem, default=''):
    if elem:
        return elem.getText(separator=' ', strip=True)
    else:
        return default
In [9]:
from dataclasses import dataclass

@dataclass
class Person:
    firstname: str
    middlename: str
    surname: str

Parser

Provo a creare un parser.

Un estratto dal file inferno.xml:

<div1>  <head>Canto 1</head>
<lg type="canto">
    <l>
      <LM lemma="il" catg="rdms">Nel</LM>
      <LM lemma="in mezzo di" catg="eilaksl">mezzo</LM>
      <LM lemma="il" catg="rdms">del</LM>
      <LM lemma="cammino" catg="sm2ms">cammin</LM>
      <LM lemma="di" catg="epskg">di</LM>
      <LM lemma="nostro" catg="as1fs">nostra</LM>
      <LM lemma="vita" catg="sf1fs">vita</LM>
    </l>
    ...
    ...
    <l>
      <LM lemma="che" catg="pr">che</LM>
      <LM1>
        <LM lemma="il" catg="rdms">nel</LM> 
        <LM lemma="in" catg="epaksl">nel</LM>
      </LM1>
      <LM lemma="pensiero" catg="sm2ms">pensier</LM>
      <LM lemma="rinnovare" catg="vta1ips3">rinova</LM>
      <LM lemma="la" catg="rdfs">la</LM>
      <LM lemma="paura" catg="sf1fs">paura</LM>!
    </l>
    <l>
      ...

Il tag \<div1> individua la porzione di file di un Canto, il tag \<l> individua un verso, il tag \<LM> individua una forma flessa, ciascuna forma flessa ha 1 o 2 attributi. All'interno di un verso può essere presente il tag \<LM1> che ha come content più elementi \<LM>, ciascuno di essi contiene la stessa forma flessa ma differenti valori per gli attributi 'catg' e 'lemma'.

per questa implementazione uso la libreria Python Beatiful Soup.

In [269]:
class TEIFile(object):
    def __init__(self, filename, idres=0):
        self.filename = filename
        self.soup = read_tei(filename)
        self._text = None
        self.idres=idres;
       # self._lemmas = None
       # self._lemma_lemmas = None
       # self._categ_lemmas = None
        self._title = ''
        self._abstract = ''

    
    @property
    def title(self):
        if not self._title:
            if  not self.soup.title:
                self._title = "na"
            else:
                self._title = self.soup.title.getText().replace('\n','').strip()
        return self._title

    
    @property
    def authors(self):
        #authors_in_header = self.soup.analytic.find_all('author')
        authors_in_header = self.soup.find_all('author')

        result = []
        for author in authors_in_header:
            persname = author.persname
            if not persname:
                continue
            firstname = elem_to_text(persname.find("forename"))#, type="first"))
            middlename = elem_to_text(persname.find("forename", type="middle"))
            surname = elem_to_text(persname.surname)
            person = Person(firstname, middlename, surname)
            result.append(person)
        return result
    
    @property
    def bibliography(self):
        bibliography = self.soup.find_all('bibl')
        result = []
        for bibl in bibliography:
            if not bibl:
                continue
            #if (elem_to_text(bibl).startswith("Enter your references here")):
            #    continue
            my_bibl_tmp=elem_to_text(bibl).replace('\n','').strip()
            my_bibl_tmp=my_bibl_tmp.replace(' .', '.')
            result.append(" ".join(my_bibl_tmp.split()))
        return result


    @property
    def text(self):
        if not self._text:
            divs_text = []
            for div in self.soup.body.find_all("div1"):
                # div is neither an appendix nor references, just plain text.
                if not div.get("type"):
                    div_text = div.get_text(separator=' ', strip=True)
                    divs_text.append(div_text)

            plain_text = " ".join(divs_text)
            self._text = plain_text
        return self._text
    
    @property
    def orderedlemma(self):
        ordr_lms = []
        i=0
        for div in self.soup.body.find_all("div1"):
            for verso in div.find_all('l'):
                i=i+1;
                j=0;
                for lm in verso.find_all("lm"):
                    lstctg=[];
                    lstlms=[];
                    j=j+1;
                    lm_text=elem_to_text(lm).strip();
                    #ctg=lm.get('catg');
                    if (lm.get('catg')!=None):
                        ctg=lm.get('catg');
                    else:
                        ctg="non_spec";
                    
                    lstctg.append(" ".join(ctg.split())); 
                    
                    if (lm.get('lemma')!=None):
                        lemma=lm.get('lemma');
                    else:
                        lemma="non_spec";
                    lstlms.append(" ".join(lemma.split()));    
                    for parent in lm.parents:
                        if (parent.name=='div1'):
                            canto = parent.contents[0];
                        if (parent.name=='lm1' and ordr_lms[-1][0]==" ".join(lm_text.split())):
                            j=j-1;
                            lstctg=lstctg+ordr_lms[-1][1];
                            lstlms=lstlms+ordr_lms[-1][2];
                            ordr_lms.pop();
                    
                    ordr_lms.append((" ".join(lm_text.split()), lstctg, lstlms, canto.replace('\n','').strip(), i, j));
               
                
                   # ordr_lms.append((" ".join(lm_text.split()), " ".join(ctg.split()), " ".join(lemma.split()), canto.replace('\n','').strip(), i, j, "hdn:Works/Commedia/Cantica/1/"+str(i),
                   #                 "hdn:Works/Commedia/Cantica/1/"+str(i)+"/#"+str(j)));
                   
                
        return ordr_lms
    #IRI del verso
    @property
    def IRIverso(self):
        iris = []
        i=0
        for div in self.soup.body.find_all("div1"):
            for verso in div.find_all('l'):
                i=i+1;
                verso_text=elem_to_text(verso).strip();
                for vparent in verso.parents:
                        if (vparent.name=='div1'):
                            canto = vparent.contents[0];
                iri_verso="http://hdn.dantenetwork.it/resource/work/commedia/cantica/"+str(self.idres)+"/"+"/".join(canto.lower().split())+"/verso/"+str(i);
                iri_verso=iri_verso+'\n a efrbroo:F2_Expression ,\n rdfs:Resource ; \nhttp://erlangen-crm.org/current/P190_has_symbolic_content "';
                iri_verso=iri_verso+(" ".join(verso_text.split())).strip()+ '\"^^xsd:string ;\n http://erlangen-crm.org/current/P3_has_note';
                iri_verso=iri_verso+' "'+str(i)+'"^^xsd:int ;\n http://hdn.dantenetwork.it/resource/has_number "'+str(i)+'"^^xsd:int .'
                iris.append((i, " ".join(verso_text.split()), iri_verso));
               
          
        return iris
    
    
    #test
    @property
    def ff_ea(self):
        lms_text = []
        lms_tupl=()
        for lm in self.soup.body.find_all("lm"):
            lm_text=elem_to_text(lm).strip()
            ctg=lm.get('catg');
            if (lm.get('lemma')!=None):
                lemma=lm.get('lemma');
            else:
                lemma="non_spec";
            #lm_text=lm_text+", "+ctg+", "+lemma;
            for parent in lm.parents:
                if (parent.name=='div1'):
                    canto = parent.contents[0]
                    break;
            lms_text.append((" ".join(lm_text.split()), " ".join(ctg.split()), " ".join(lemma.split()), canto.replace('\n','').strip()));               
        return lms_text
    
    @property
    def categ_lemma(self):
        ctgs_text = []
        for lm in self.soup.body.find_all("lm"):
            ctg_text=lm.get('catg').strip();
            ctgs_text.append(" ".join(ctg_text.split()))
        return ctgs_text
    
    @property
    def lemma_lemma(self):
        lemmas_text = []
        for lm in self.soup.body.find_all("lm"):
            if (lm.get('lemma')):
                lemma_text=lm.get('lemma').strip();
            else:
                lemma_text='non_spec';
            lemmas_text.append(" ".join(lemma_text.split()))
        return lemmas_text
In [235]:
def tei_to_csv_entry(tei_file, idres=0):
    tei = TEIFile(tei_file, idres)
    print(f"Handled {tei_file}")
    base_name = tei_file
    return tei.orderedlemma, tei.IRIverso, tei.categ_lemma, tei.lemma_lemma  #, tei.abstract

Provo a vedere se il parser funziona

Dovrebbe arrivare sino al termine 'oscuro', controllare!

In [219]:
tei = TEIFile('/Users/cesare/Projects/hdn/triple/DanteTriple/xml/DanteSearch/grammaticale/inferno_forparsing.xml', 1)
bbs=tei.ff_ea
for re in bbs:
    print (re, end="\n"*2)
    if (re[0].startswith('oscura')):
        print('...')
        break
('Nel', 'rdms', 'il', 'Canto 1')

('mezzo', 'eilaksl', 'in mezzo di', 'Canto 1')

('del', 'rdms', 'il', 'Canto 1')

('cammin', 'sm2ms', 'cammino', 'Canto 1')

('di', 'epskg', 'di', 'Canto 1')

('nostra', 'as1fs', 'nostro', 'Canto 1')

('vita', 'sf1fs', 'vita', 'Canto 1')

('mi', 'pf1sypr', 'mi', 'Canto 1')

('ritrovai', 'vta+1irs1', 'ritrovare', 'Canto 1')

('per', 'epskpl', 'per', 'Canto 1')

('una', 'rifs', 'una', 'Canto 1')

('selva', 'sf1fs', 'selva', 'Canto 1')

('oscura', 'a1fs', 'oscuro', 'Canto 1')

...

Carico il testo inferno.xml e creo una tabella

Eseguo il parsing del testo presente nel file e creo una tabella con le seguenti colonne: forma flessa, categoria, lemma, canto, verso, pposizione forma flessa nel verso

In [270]:
mytesto=tei_to_csv_entry('/Users/cesare/Projects/hdn/triple/DanteTriple/xml/DanteSearch/grammaticale/inferno_forparsing.xml', 1)
Handled /Users/cesare/Projects/hdn/triple/DanteTriple/xml/DanteSearch/grammaticale/inferno_forparsing.xml
In [271]:
data = [mytesto[0]]
#data[0]
dfObj = pd.DataFrame(data[0]) 
testo_tabella=pd.DataFrame(data[0], columns = ['FormaFlessa' , 'Categoria', 'Lemma', 'Canto', 'Verso', 'PosizioneFFNelVerso']) 
testo_tabella.count()
Out[271]:
FormaFlessa            33400
Categoria              33400
Lemma                  33400
Canto                  33400
Verso                  33400
PosizioneFFNelVerso    33400
dtype: int64
In [272]:
testo_tabella.head(10)
Out[272]:
FormaFlessa Categoria Lemma Canto Verso PosizioneFFNelVerso
0 Nel [rdms] [il] Canto 1 1 1
1 mezzo [eilaksl] [in mezzo di] Canto 1 1 2
2 del [rdms] [il] Canto 1 1 3
3 cammin [sm2ms] [cammino] Canto 1 1 4
4 di [epskg] [di] Canto 1 1 5
5 nostra [as1fs] [nostro] Canto 1 1 6
6 vita [sf1fs] [vita] Canto 1 1 7
7 mi [pf1sypr] [mi] Canto 1 2 1
8 ritrovai [vta+1irs1] [ritrovare] Canto 1 2 2
9 per [epskpl] [per] Canto 1 2 3

Generiamo una tabella con gli IRI dei versi per la cantica Inferno

La abella contiene il numero del verso, il verso e l'IRI del verso.
Per l'IRI del verso mi son basato su quanto riportato nel file Commedia.rdf, un esempio è il seguente:

http://hdn.dantenetwork.it/resource/work/commedia/cantica/2/canto/9/verso/106
a efrbroo:F2_Expression , rdfs:Resource ;
http://erlangen-crm.org/current/P190_has_symbolic_content
"Per li tre gradi sù di buona voglia"^^xsd:string ;
http://erlangen-crm.org/current/P3_has_note
"106"^^xsd:int ;
http://hdn.dantenetwork.it/resource/has_number
"106"^^xsd:int .

In [280]:
data_IRI_versi_inf = [mytesto[1]]
#data_IRI_versi
df_IRI_versi_inf=pd.DataFrame(data_IRI_versi_inf[0], columns = ['NumeroVerso', 'Verso' , 'IRIVerso']) 
df_IRI_versi_inf.count()
Out[280]:
NumeroVerso    4721
Verso          4721
IRIVerso       4721
dtype: int64
In [282]:
df_IRI_versi_inf.head().style.set_properties(subset=['IRIVerso'], **{'width': '400px'})
Out[282]:
NumeroVerso Verso IRIVerso
0 1 Nel mezzo del cammin di nostra vita http://hdn.dantenetwork.it/resource/work/commedia/cantica/1/canto/1/verso/1 a efrbroo:F2_Expression , rdfs:Resource ; http://erlangen-crm.org/current/P190_has_symbolic_content "Nel mezzo del cammin di nostra vita"^^xsd:string ; http://erlangen-crm.org/current/P3_has_note "1"^^xsd:int ; http://hdn.dantenetwork.it/resource/has_number "1"^^xsd:int .
1 2 mi ritrovai per una selva oscura http://hdn.dantenetwork.it/resource/work/commedia/cantica/1/canto/1/verso/2 a efrbroo:F2_Expression , rdfs:Resource ; http://erlangen-crm.org/current/P190_has_symbolic_content "mi ritrovai per una selva oscura"^^xsd:string ; http://erlangen-crm.org/current/P3_has_note "2"^^xsd:int ; http://hdn.dantenetwork.it/resource/has_number "2"^^xsd:int .
2 3 ché la diritta via era smarrita . http://hdn.dantenetwork.it/resource/work/commedia/cantica/1/canto/1/verso/3 a efrbroo:F2_Expression , rdfs:Resource ; http://erlangen-crm.org/current/P190_has_symbolic_content "ché la diritta via era smarrita ."^^xsd:string ; http://erlangen-crm.org/current/P3_has_note "3"^^xsd:int ; http://hdn.dantenetwork.it/resource/has_number "3"^^xsd:int .
3 4 Ahi quanto a dir qual era è cosa dura http://hdn.dantenetwork.it/resource/work/commedia/cantica/1/canto/1/verso/4 a efrbroo:F2_Expression , rdfs:Resource ; http://erlangen-crm.org/current/P190_has_symbolic_content "Ahi quanto a dir qual era è cosa dura"^^xsd:string ; http://erlangen-crm.org/current/P3_has_note "4"^^xsd:int ; http://hdn.dantenetwork.it/resource/has_number "4"^^xsd:int .
4 5 esta selva selvaggia e aspra e forte http://hdn.dantenetwork.it/resource/work/commedia/cantica/1/canto/1/verso/5 a efrbroo:F2_Expression , rdfs:Resource ; http://erlangen-crm.org/current/P190_has_symbolic_content "esta selva selvaggia e aspra e forte"^^xsd:string ; http://erlangen-crm.org/current/P3_has_note "5"^^xsd:int ; http://hdn.dantenetwork.it/resource/has_number "5"^^xsd:int .

File purgatorio.xml

Test, ignorare.

In [276]:
#tei_purgatorio = TEIFile('/Users/cesare/Projects/hdn/triple/DanteTriple/xml/DanteSearch/grammaticale/purgatorio.xml', 2)
#bbs_pu=tei_purgatorio.IRIverso
#for repu in bbs_pu:
#    print (repu, end="\n"*2)
#    if (repu[0].startswith('che')):
#        print('...')
#        break

Carico il testo purgatorio.xml e creo una tabella

Eseguo il parsing del testo presente nel file e creo una tabella simile alla precedente \<LM1> forma flessa con due lemmi, gestire nel parser

In [277]:
parsed_purgatorio=tei_to_csv_entry('/Users/cesare/Projects/hdn/triple/DanteTriple/xml/DanteSearch/grammaticale/purgatorio.xml', 2)
Handled /Users/cesare/Projects/hdn/triple/DanteTriple/xml/DanteSearch/grammaticale/purgatorio.xml
In [278]:
#DA COMPLETARE CON IRI CORRETTO!

data_purgatorio = [parsed_purgatorio[0]]
#dfObj_purgatorio = pd.DataFrame(data_purgatorio[0]) 
testo_purgatorio_tabella=pd.DataFrame(data_purgatorio[0], columns = ['FormaFlessa' , 'Categoria', 'Lemma', 'Canto', 'Verso', 'PosizioneFFNelVerso']) 
testo_purgatorio_tabella.count()
Out[278]:
FormaFlessa            33245
Categoria              33245
Lemma                  33245
Canto                  33245
Verso                  33245
PosizioneFFNelVerso    33245
dtype: int64
In [279]:
testo_purgatorio_tabella.tail()
Out[279]:
FormaFlessa Categoria Lemma Canto Verso PosizioneFFNelVerso
33240 disposto [vtp2pra1ms] [disporre] Canto 33 4755 3
33241 a [epsb] [a] Canto 33 4755 4
33242 salire [vi3fp] [salire] Canto 33 4755 5
33243 alle [rdfp, epakml] [la, a] Canto 33 4755 6
33244 stelle [sf1fp] [stella] Canto 33 4755 7

Generiamo una tabella con gli IRI dei versi per la cantica Purgatorio

La tabella contiene il numero del verso, il verso e l'IRI del verso.

In [284]:
data_IRI_versi_pur = [parsed_purgatorio[1]]
#data_IRI_versi
df_IRI_versi_pur=pd.DataFrame(data_IRI_versi_pur[0], columns = ['NumeroVerso', 'Verso' , 'IRIVerso']) 
df_IRI_versi_pur.count()
Out[284]:
NumeroVerso    4755
Verso          4755
IRIVerso       4755
dtype: int64
In [285]:
df_IRI_versi_pur.head().style.set_properties(subset=['IRIVerso'], **{'width': '400px'})
Out[285]:
NumeroVerso Verso IRIVerso
0 1 Per correr miglior acque alza le vele http://hdn.dantenetwork.it/resource/work/commedia/cantica/2/canto/1/verso/1 a efrbroo:F2_Expression , rdfs:Resource ; http://erlangen-crm.org/current/P190_has_symbolic_content "Per correr miglior acque alza le vele"^^xsd:string ; http://erlangen-crm.org/current/P3_has_note "1"^^xsd:int ; http://hdn.dantenetwork.it/resource/has_number "1"^^xsd:int .
1 2 omai la navicella del del mio ingegno , http://hdn.dantenetwork.it/resource/work/commedia/cantica/2/canto/1/verso/2 a efrbroo:F2_Expression , rdfs:Resource ; http://erlangen-crm.org/current/P190_has_symbolic_content "omai la navicella del del mio ingegno ,"^^xsd:string ; http://erlangen-crm.org/current/P3_has_note "2"^^xsd:int ; http://hdn.dantenetwork.it/resource/has_number "2"^^xsd:int .
2 3 che lascia dietro a sé mar sì crudele; http://hdn.dantenetwork.it/resource/work/commedia/cantica/2/canto/1/verso/3 a efrbroo:F2_Expression , rdfs:Resource ; http://erlangen-crm.org/current/P190_has_symbolic_content "che lascia dietro a sé mar sì crudele;"^^xsd:string ; http://erlangen-crm.org/current/P3_has_note "3"^^xsd:int ; http://hdn.dantenetwork.it/resource/has_number "3"^^xsd:int .
3 4 e canterò di quel secondo regno http://hdn.dantenetwork.it/resource/work/commedia/cantica/2/canto/1/verso/4 a efrbroo:F2_Expression , rdfs:Resource ; http://erlangen-crm.org/current/P190_has_symbolic_content "e canterò di quel secondo regno"^^xsd:string ; http://erlangen-crm.org/current/P3_has_note "4"^^xsd:int ; http://hdn.dantenetwork.it/resource/has_number "4"^^xsd:int .
4 5 dove l' umano spirito si purga http://hdn.dantenetwork.it/resource/work/commedia/cantica/2/canto/1/verso/5 a efrbroo:F2_Expression , rdfs:Resource ; http://erlangen-crm.org/current/P190_has_symbolic_content "dove l' umano spirito si purga"^^xsd:string ; http://erlangen-crm.org/current/P3_has_note "5"^^xsd:int ; http://hdn.dantenetwork.it/resource/has_number "5"^^xsd:int .