Atlantis is the Sardo Corso Graben Horst underwater continental block submerged by the Meltwater Pulses and destroyed by a subduction zone, Capital is Sulcis
# https://github.com/luigiusai/LuigiUsaiTools/blob/main/StrumentiTestualiUSAI.py
# StrumentiTestualiUsai.py
# Progetto Integrato per l'Analisi Testuale e Narratologica
# Autore: Luigi Usai (con assistenza AI)
# Data: 19 Maggio 2025
# Versione: 2.0 (Unificazione e Miglioramenti)
#
# Questo script Tkinter fornisce un'interfaccia grafica per caricare corpora testuali
# ed eseguire varie analisi linguistiche, di usabilità, narratologiche e preliminari Griceane.
#
# Funzionalità incluse:
# - Caricamento di file di testo (.txt) come corpus.
# - Gestione (aggiunta/rimozione) di stopwords.
# - Analisi di frequenza dei termini.
# - Generazione di Nuvole di Parole.
# - Analisi di Collocazioni (N-grammi).
# - KWIC (Parole Chiave nel Contesto).
# - Andamento dei Termini attraverso i documenti o segmenti.
# - Rete di Co-occorrenze testuali.
# - Suddivisione in Frasi e Token (usabilità).
# - Annotazione Morfosintattica (POS Tagging - usabilità).
# - Calcolo Indice di Leggibilità Gulpease (globale e per frase - usabilità, specifico italiano).
# - Creazione e Visualizzazione Matrice Attanziale di Greimas (narratologia).
# - Creazione e Visualizzazione Matrice Funzioni di Propp (narratologia).
# - Creazione e Visualizzazione Tensori Narrativi (Luigi Usai - narratologia).
# - Generazione Permutazioni di Funzioni di Propp (trame possibili).
# - Generazione Combinazioni di Funzioni di Propp (sottoinsiemi di funzioni).
# - Visualizzazione Grafica Sequenza Funzioni di Propp (richiede Graphviz).
# - Analisi Semplificata Indicatori Griceani (Quantità, Modo, Qualità - richiede NLTK).
# - Salvataggio Dati Narratologici (JSON, SQLite).
# - Finestra "About" con informazioni sull'autore.
#
# Dipendenze richieste:
# - tkinter (standard Python)
# - wordcloud (pip install wordcloud)
# - matplotlib (pip install matplotlib)
# - Pillow (PIL) (pip install Pillow) - Per l'immagine nell'About
# - nltk (pip install nltk) - Per tokenizzazione, POS tagging, Gulpease, Grice
# - graphviz (pip install graphviz) - Per visualizzazione sequenze Propp
# - itertools (standard Python)
# - json (standard Python)
# - sqlite3 (standard Python)
# - re (standard Python)
# - collections (standard Python)
# - statistics (standard Python)
# - math (standard Python)
# - textwrap (standard Python)
#
# Assicurati di scaricare i dati NLTK necessari (punkt, averaged_perceptron_tagger)
# Eseguendo in un interprete Python:
# import nltk
# nltk.download('punkt')
# nltk.download('averaged_perceptron_tagger')
#
# Assicurati che il software Graphviz sia installato sul sistema e nel PATH per la visualizzazione grafica di Propp:
# https://graphviz.org/download/
#
# Per una panoramica sui Tensori Narrativi:
# https://www.amazon.it/ARCHITETTURE-INVISIBILI-VIAGGIO-NARRAZIONE-Versione/dp/B0F91P1XBH/
# Riferimento principale delle opere: Harvard Dataverse, DOI:10.7910/DVN/ICOJ19
#
import tkinter as tk
from tkinter import filedialog, messagebox, scrolledtext, simpledialog
import json
import sqlite3
import itertools
import re
from collections import Counter
import statistics
import math
import textwrap # Per gestire il testo lungo nei nodi graphviz
# --- Gestione Import Opzionali e Dipendenze ---
# Controlla la disponibilità delle librerie non standard e dei dati NLTK
# Pillow (PIL) per immagine About
pil_disponibile = False
try:
from PIL import Image, ImageTk
pil_disponibile = True
except ImportError:
print("Pillow (PIL) non è installato. L'immagine nell'About non sarà visualizzata. Installa con: pip install Pillow")
# NLTK per analisi linguistiche, usabilità e Grice
nltk_disponibile = False
nltk_punkt_disponibile = False
nltk_tagger_disponibile = False
try:
import nltk
nltk_disponibile = True
# Verifica la presenza dei dati NLTK necessari
try:
nltk.data.find('tokenizers/punkt')
nltk_punkt_disponibile = True
except nltk.downloader.DownloadError:
print("Pacchetto NLTK 'punkt' non trovato. Alcune funzionalità (frasi, token, leggibilità, Grice) potrebbero non funzionare.")
print("Scaricalo eseguendo in Python: nltk.download('punkt')")
except LookupError:
print("Pacchetto NLTK 'punkt' non trovato. Alcune funzionalità (frasi, token, leggibilità, Grice) potrebbero non funzionare.")
print("Scaricalo eseguendo in Python: nltk.download('punkt')")
try:
nltk.data.find('taggers/averaged_perceptron_tagger')
nltk_tagger_disponibile = True
except nltk.downloader.DownloadError:
print("Pacchetto NLTK 'averaged_perceptron_tagger' non trovato. Il POS tagging potrebbe non funzionare.")
print("Scaricalo eseguendo in Python: nltk.download('averaged_perceptron_tagger')")
except LookupError:
print("Pacchetto NLTK 'averaged_perceptron_tagger' non trovato. Il POS tagging potrebbe non funzionare.")
print("Scaricalo eseguendo in Python: nltk.download('averaged_perceptron_tagger')")
except ImportError:
print("Libreria NLTK non trovata. Le funzionalità di usabilità e Grice non saranno disponibili. Installa con: pip install nltk")
# Graphviz per visualizzazione Propp
graphviz_disponibile = False
try:
import graphviz
graphviz_disponibile = True
except ImportError:
print("Libreria 'graphviz' non trovata. La visualizzazione delle sequenze di Propp non sarà disponibile.")
print("Installala con: pip install graphviz")
print("Inoltre, assicurati che il software Graphviz sia installato sul sistema e nel PATH: https://graphviz.org/download/")
# WordCloud e Matplotlib per nuvola di parole e andamento termini
wordcloud_disponibile = False
matplotlib_disponibile = False
try:
from wordcloud import WordCloud
import matplotlib.pyplot as plt
wordcloud_disponibile = True
matplotlib_disponibile = True
except ImportError:
print("Librerie 'wordcloud' o 'matplotlib' non trovate. La nuvola di parole e l'andamento termini non saranno disponibili.")
print("Installale con: pip install wordcloud matplotlib")
# --- Costanti e Definizioni ---
# Definizioni delle 31 funzioni di Propp
FUNZIONI_PROPP = {
"F1": "Allontanamento (Un membro della famiglia si allontana)",
"F2": "Divieto (All'eroe è imposto un divieto)",
"F3": "Infrazione (Il divieto è infranto)",
"F4": "Investigazione (L'antagonista tenta una ricognizione)",
"F5": "Delazione (L'antagonista riceve informazioni sulla vittima)",
"F6": "Tranello (L'antagonista tenta di ingannare la vittima)",
"F7": "Connivenza (La vittima cade nell'inganno)",
"F8": "Danneggiamento/Mancanza (L'antagonista danneggia o causa una mancanza)",
"F9": "Mediazione (Il danneggiamento/mancanza è reso noto, l'eroe è sollecitato)",
"F10": "Consenso dell'Eroe (L'eroe accetta di reagire)",
"F11": "Partenza dell'Eroe (L'eroe lascia la casa)",
"F12": "Messa alla Prova (L'eroe è messo alla prova dal donatore)",
"F13": "Reazione dell'Eroe (L'eroe reagisce alle azioni del donatore)",
"F14": "Conseguimento Mezzo Magico (L'eroe ottiene un mezzo magico)",
"F15": "Trasferimento (L'eroe è trasferito vicino all'oggetto della ricerca)",
"F16": "Lotta (L'eroe e l'antagonista si scontrano)",
"F17": "Marchiatura (All'eroe è impresso un marchio)",
"F18": "Vittoria (L'antagonista è sconfitto)",
"F19": "Rimozione Danno/Mancanza (Il danno/mancanza iniziale è rimosso)",
"F20": "Ritorno dell'Eroe (L'eroe ritorna)",
"F21": "Persecuzione (L'eroe è perseguitato)",
"F22": "Salvataggio (L'eroe è salvato dalla persecuzione)",
"F23": "Arrivo in Incognito (L'eroe arriva non riconosciuto)",
"F24": "Pretese Falso Eroe (Un falso eroe avanza pretese)",
"F25": "Compito Difficile (All'eroe è proposto un compito difficile)",
"F26": "Adempimento Compito (Il compito è portato a termine)",
"F27": "Riconoscimento (L'eroe è riconosciuto)",
"F28": "Smascheramento (Il falso eroe o l'antagonista è smascherato)",
"F29": "Trasfigurazione (All'eroe è data una nuova apparenza)",
"F30": "Punizione (L'antagonista è punito)",
"F31": "Nozze/Ricompensa (L'eroe si sposa o è ricompensato)"
}
# Lista (molto limitata) di possibili indicatori di "hedging" (copertura/incertezza) per Grice
HEDGING_TERMS = ["credo", "penso", "forse", "magari", "sembra", "parrebbe", "apparentemente", "in un certo senso", "tipo", "cioè", "insomma"]
# --- Classi per Funzionalità Specifiche ---
class FunzioniUsability:
"""Contiene funzioni per l'analisi di usabilità e leggibilità del testo."""
def __init__(self, app_ref):
self.app_ref = app_ref
self.lingua_analisi = "italian" # Default per funzioni come Gulpease
def imposta_lingua_analisi(self):
"""Permette all'utente di impostare la lingua per le analisi che la supportano."""
lingua_scelta = simpledialog.askstring("Imposta Lingua Analisi",
"Scegli la lingua per l'analisi (es. 'italian', 'english').\n"
"Nota: Gulpease funziona solo per l'italiano.",
initialvalue=self.lingua_analisi,
parent=self.app_ref.root)
if lingua_scelta:
lingua_scelta_lower = lingua_scelta.strip().lower()
# Aggiungere altre lingue supportate da NLTK se necessario
if lingua_scelta_lower in ['italian', 'english']:
self.lingua_analisi = lingua_scelta_lower
messagebox.showinfo("Lingua Impostata", f"Lingua per l'analisi impostata a: {self.lingua_analisi}", parent=self.app_ref.root)
self.app_ref._display_output("Impostazione Lingua", f"Lingua analisi: {self.lingua_analisi}")
else:
messagebox.showwarning("Lingua non Supportata", f"Lingua '{lingua_scelta}' non supportata o riconosciuta.\nMantengo: {self.lingua_analisi}", parent=self.app_ref.root)
def _check_corpus_e_nltk(self, check_punkt=False, check_tagger=False):
"""Controlla se il corpus è caricato e se NLTK e i suoi componenti sono disponibili."""
if not self.app_ref.corpus_testuale:
messagebox.showwarning("Corpus Vuoto", "Per favore, carica prima un corpus testuale.", parent=self.app_ref.root)
return False
if not nltk_disponibile:
messagebox.showerror("NLTK Mancante", "La libreria NLTK è necessaria per questa funzionalità.", parent=self.app_ref.root)
return False
if check_punkt and not nltk_punkt_disponibile:
messagebox.showerror("Dipendenza NLTK Mancante", "Il pacchetto 'punkt' di NLTK è necessario per questa funzionalità.\nScaricalo eseguendo in Python: nltk.download('punkt')", parent=self.app_ref.root)
return False
if check_tagger and not nltk_tagger_disponibile:
messagebox.showerror("Dipendenza NLTK Mancante", "Il pacchetto 'averaged_perceptron_tagger' di NLTK è necessario per questa funzionalità.\nScaricalo eseguendo in Python: nltk.download('averaged_perceptron_tagger')", parent=self.app_ref.root)
return False
return True
def subdividi_in_frasi(self):
"""Suddivide il corpus in frasi e le visualizza."""
if not self._check_corpus_e_nltk(check_punkt=True):
return
testo_completo = ' '.join(self.app_ref.corpus_testuale)
try:
frasi = nltk.sent_tokenize(testo_completo, language=self.lingua_analisi)
output_str = f"Suddivisione in Frasi (Lingua: {self.lingua_analisi}):\n"
output_str += "-------------------------------------------------\n"
if not frasi:
output_str += "Nessuna frase trovata."
else:
# Limita la visualizzazione per testi molto lunghi
max_frasi_visualizzate = 500
for i, frase in enumerate(frasi):
if i >= max_frasi_visualizzate:
output_str += f"\n... e altre {len(frasi) - max_frasi_visualizzate} frasi non visualizzate."
break
output_str += f"Frase {i+1}: {frase}\n"
self.app_ref._display_output("Suddivisione in Frasi", output_str)
except Exception as e:
messagebox.showerror("Errore Suddivisione Frasi", f"Errore: {e}", parent=self.app_ref.root)
self.app_ref._display_output("Errore Suddivisione Frasi", f"Errore: {e}")
def subdividi_in_token(self):
"""Suddivide il corpus in token e li visualizza."""
if not self._check_corpus_e_nltk(check_punkt=True):
return
testo_completo = ' '.join(self.app_ref.corpus_testuale)
try:
# word_tokenize usa 'punkt' internamente e può prendere un argomento 'language'.
tokens = nltk.word_tokenize(testo_completo, language=self.lingua_analisi)
output_str = f"Suddivisione in Token (Lingua: {self.lingua_analisi}):\n"
output_str += "--------------------------------------------------\n"
if not tokens:
output_str += "Nessun token trovato."
else:
# Limita la visualizzazione per testi molto lunghi
max_tokens_visualizzati = 1000
display_tokens = tokens[:max_tokens_visualizzati]
output_str += ", ".join(display_tokens)
if len(tokens) > max_tokens_visualizzati:
output_str += f"\n... e altri {len(tokens) - max_tokens_visualizzati} token non visualizzati."
self.app_ref._display_output("Suddivisione in Token", output_str)
except Exception as e:
messagebox.showerror("Errore Suddivisione Token", f"Errore: {e}", parent=self.app_ref.root)
self.app_ref._display_output("Errore Suddivisione Token", f"Errore: {e}")
def annotazione_pos(self):
"""Esegue il Part-of-Speech tagging sul corpus e visualizza i risultati."""
if not self._check_corpus_e_nltk(check_punkt=True, check_tagger=True):
return
testo_completo = ' '.join(self.app_ref.corpus_testuale)
try:
tokens = nltk.word_tokenize(testo_completo, language=self.lingua_analisi)
# nltk.pos_tag usa il tagger 'averaged_perceptron_tagger'.
# Per l'italiano, i risultati potrebbero non essere ottimali senza un modello specifico.
# Usiamo quello di default e avvisiamo l'utente.
tagged_tokens = nltk.pos_tag(tokens) # Non c'è un argomento 'language' diretto per il tagger qui
output_str = f"Annotazione Morfosintattica (POS Tagging - Lingua: {self.lingua_analisi}):\n"
output_str += "-------------------------------------------------------------------\n"
if not tagged_tokens:
output_str += "Nessun token da annotare."
else:
# Limita la visualizzazione
max_tagged_visualizzati = 500
for i, (token, tag) in enumerate(tagged_tokens):
if i >= max_tagged_visualizzati:
output_str += f"\n... e altri {len(tagged_tokens) - max_tagged_visualizzati} token non visualizzati."
break
output_str += f"{token} [{tag}]\n"
output_str += "\nNota: Il tagger predefinito di NLTK ('averaged_perceptron_tagger') è ottimizzato per l'inglese."
output_str += "\nPer l'italiano, i risultati potrebbero non essere ottimali senza un modello specifico addestrato."
self.app_ref._display_output("Annotazione POS", output_str)
except Exception as e:
messagebox.showerror("Errore Annotazione POS", f"Errore: {e}", parent=self.app_ref.root)
self.app_ref._display_output("Errore Annotazione POS", f"Errore: {e}")
def calcola_gulpease_globale(self):
"""
Calcola l'indice di leggibilità Gulpease per l'intero corpus.
Questa funzione è specifica per la lingua ITALIANA.
"""
if self.lingua_analisi != "italian":
messagebox.showwarning("Lingua non Adatta", "L'indice Gulpease è calibrato per la lingua italiana. "
f"La lingua attualmente impostata è '{self.lingua_analisi}'. "
"Il risultato potrebbe non essere attendibile.", parent=self.app_ref.root)
if not self._check_corpus_e_nltk(check_punkt=True): # Necessario per frasi e parole
return
testo_completo = ' '.join(self.app_ref.corpus_testuale)
try:
# Tokenizzazione parole (solo alfabetiche)
# Forziamo italiano per la tokenizzazione specifica per Gulpease
parole_raw = nltk.word_tokenize(testo_completo.lower(), language='italian')
parole = [p for p in parole_raw if p.isalpha()]
num_parole = len(parole)
if num_parole == 0:
self.app_ref._display_output("Indice Gulpease", "Nessuna parola alfabetica valida trovata per il calcolo.")
messagebox.showwarning("Indice Gulpease", "Nessuna parola valida trovata per il calcolo.", parent=self.app_ref.root)
return
# Tokenizzazione frasi
# Forziamo italiano per la tokenizzazione specifica per Gulpease
frasi = nltk.sent_tokenize(testo_completo, language='italian')
num_frasi = len(frasi)
if num_frasi == 0:
self.app_ref._display_output("Indice Gulpease", "Nessuna frase trovata per il calcolo.")
messagebox.showwarning("Indice Gulpease", "Nessuna frase trovata per il calcolo.", parent=self.app_ref.root)
return
num_lettere = sum(len(p) for p in parole)
# Formula Gulpease: G = 89 + ( ( (Frasi * 100) / Parole * 3 ) - ( (Lettere * 100) / Parole * 10 ) ) / 100
# Semplificata: G = 89 + (Frasi * 300 / Parole) - (Lettere * 10 / Parole)
# G = 89 + ( (Frasi * 300) - (Lettere * 10) ) / Parole
# Evita divisione per zero, anche se num_parole > 0 è già stato controllato
if num_parole > 0:
gulpease_index = 89 + ( (num_frasi * 300) - (num_lettere * 10) ) / num_parole
else:
gulpease_index = 0 # O altro valore indicativo
# Cap e Floor dell'indice
gulpease_index = max(0, min(100, gulpease_index))
interpretazione = ""
if gulpease_index >= 80: interpretazione = "Molto facile (lettori con licenza elementare)"
elif gulpease_index >= 60: interpretazione = "Facile (lettori con licenza media inferiore)"
elif gulpease_index >= 40: interpretazione = "Abbastanza difficile (lettori con licenza media superiore)"
else: interpretazione = "Difficile (lettori con laurea)"
output_str = f"Indice di Leggibilità Globale Gulpease (per l'italiano):\n"
output_str += "-------------------------------------------------------\n"
output_str += f"Numero di Lettere (alfabetiche): {num_lettere}\n"
output_str += f"Numero di Parole (alfabetiche): {num_parole}\n"
output_str += f"Numero di Frasi: {num_frasi}\n"
output_str += f"Indice Gulpease: {gulpease_index:.2f}\n"
output_str += f"Interpretazione: {interpretazione}\n\n"
output_str += "Scala di riferimento Gulpease:\n"
output_str += " > 80: Molto facile\n 60-80: Facile\n 40-60: Abbastanza difficile\n < 40: Difficile\n"
if self.lingua_analisi != "italian":
output_str += f"\nATTENZIONE: Calcolato usando metriche italiane su testo potenzialmente non italiano ('{self.lingua_analisi}')."
self.app_ref._display_output("Indice Gulpease Globale", output_str)
except Exception as e:
messagebox.showerror("Errore Gulpease", f"Errore durante il calcolo dell'indice Gulpease: {e}", parent=self.app_ref.root)
self.app_ref._display_output("Errore Gulpease", f"Errore: {e}")
def analisi_leggibilita_per_frase(self):
"""
Calcola l'indice Gulpease per ogni frase del testo.
Utile per la "proiezione della leggibilità sul testo".
Specifico per ITALIANO.
"""
if self.lingua_analisi != "italian":
messagebox.showwarning("Lingua non Adatta", "L'indice Gulpease è calibrato per la lingua italiana. "
f"La lingua attualmente impostata è '{self.lingua_analisi}'. "
"I risultati per frase potrebbero non essere attendibili.", parent=self.app_ref.root)
if not self._check_corpus_e_nltk(check_punkt=True):
return
testo_completo = ' '.join(self.app_ref.corpus_testuale)
try:
frasi_originali = nltk.sent_tokenize(testo_completo, language='italian') # Forziamo italiano
if not frasi_originali:
self.app_ref._display_output("Leggibilità per Frase", "Nessuna frase trovata.")
messagebox.showwarning("Leggibilità per Frase", "Nessuna frase trovata.", parent=self.app_ref.root)
return
output_str = f"Analisi Leggibilità per Frase (Indice Gulpease - per l'italiano):\n"
output_str += "-----------------------------------------------------------------\n"
risultati_frasi = []
# Limita la visualizzazione per evitare output eccessivi
max_frasi_visualizzate = 300
for i, frase_txt in enumerate(frasi_originali):
if i >= max_frasi_visualizzate:
risultati_frasi.append(f"\n--- (Visualizzazione limitata alle prime {max_frasi_visualizzate} frasi) ---")
break
parole_raw_frase = nltk.word_tokenize(frase_txt.lower(), language='italian')
parole_frase = [p for p in parole_raw_frase if p.isalpha()]
num_parole_frase = len(parole_frase)
if num_parole_frase == 0:
risultati_frasi.append(f"Frase {i+1}: \"{frase_txt[:70]}...\" - Indice Gulpease: N/A (0 parole)")
continue
num_lettere_frase = sum(len(p) for p in parole_frase)
# Gulpease per una singola frase (num_frasi = 1)
# Evita divisione per zero, anche se num_parole_frase > 0 è già stato controllato
if num_parole_frase > 0:
gulpease_frase = 89 + ( (1 * 300) - (num_lettere_frase * 10) ) / num_parole_frase
else:
gulpease_frase = 0 # O altro valore indicativo
gulpease_frase = max(0, min(100, gulpease_frase))
interpretazione_frase = ""
if gulpease_frase >= 80: interpretazione_frase = "Molto facile"
elif gulpease_frase >= 60: interpretazione_frase = "Facile"
elif gulpease_frase >= 40: interpretazione_frase = "Abb. difficile"
else: interpretazione_frase = "Difficile"
risultati_frasi.append(f"Frase {i+1}: \"{frase_txt[:70]}...\"\n Indice Gulpease: {gulpease_frase:.2f} ({interpretazione_frase}) "
f"[L:{num_lettere_frase}, P:{num_parole_frase}]")
output_str += "\n\n".join(risultati_frasi)
if self.lingua_analisi != "italian":
output_str += f"\n\nATTENZIONE: Calcolato usando metriche italiane su testo potenzialmente non italiano ('{self.lingua_analisi}')."
self.app_ref._display_output("Leggibilità per Frase (Gulpease)", output_str)
except Exception as e:
messagebox.showerror("Errore Leggibilità per Frase", f"Errore: {e}", parent=self.app_ref.root)
self.app_ref._display_output("Errore Leggibilità per Frase", f"Errore: {e}")
class FunzioniNarratologia:
"""Contiene funzioni per l'analisi e la generazione basata su modelli narratologici (Greimas, Propp, Tensori)."""
def __init__(self, app_ref):
self.app_ref = app_ref
self.matrice_greimas_data = None
self.matrice_propp_data_utente = None # Dati Propp definiti dall'utente
self.tensori_narrativi_data = None
def get_propp_function_description(self, code):
"""Restituisce la descrizione completa di una funzione di Propp dato il suo codice."""
# Usa le funzioni utente se definite, altrimenti le standard
funzioni_di_riferimento = self.matrice_propp_data_utente if self.matrice_propp_data_utente is not None else FUNZIONI_PROPP
return funzioni_di_riferimento.get(code.upper(), f"Funzione Sconosciuta ({code})")
def crea_matrice_greimas(self):
"""Permette all'utente di definire e visualizzare la Matrice Attanziale di Greimas."""
messagebox.showinfo("Matrice Attanziale di Greimas",
"Definisci i ruoli attanziali (Soggetto, Oggetto, Destinante, Destinatario, Aiutante, Oppositore) per la tua analisi.",
parent=self.app_ref.root)
dialog = tk.Toplevel(self.app_ref.root)
dialog.title("Matrice Attanziale di Greimas")
dialog.geometry("400x350")
dialog.transient(self.app_ref.root)
dialog.grab_set()
tk.Label(dialog, text="Definisci gli attanti:", font=("Arial", 12, "bold")).pack(pady=10)
attanti = ["Soggetto", "Oggetto", "Destinante", "Destinatario", "Aiutante", "Oppositore"]
entries = {}
for attante in attanti:
frame = tk.Frame(dialog)
frame.pack(fill=tk.X, padx=20, pady=5)
tk.Label(frame, text=f"{attante}:", width=15, anchor="w").pack(side=tk.LEFT)
entry = tk.Entry(frame, width=30)
entry.pack(side=tk.RIGHT, expand=True, fill=tk.X)
# Pre-popola se ci sono dati salvati
if self.matrice_greimas_data and attante in self.matrice_greimas_data:
entry.insert(0, self.matrice_greimas_data[attante])
def salva_dati_greimas():
self.matrice_greimas_data = {attante: entries[attante].get().strip() for attante in attanti}
output_str = "Matrice Attanziale di Greimas:\n"
output_str += "-----------------------------\n"
for attante, valore in self.matrice_greimas_data.items():
output_str += f" - {attante}: {valore if valore else '[Non definito]'}\n"
self.app_ref._display_output("Matrice Attanziale (Greimas)", output_str)
messagebox.showinfo("Matrice Greimas", "Matrice di Greimas aggiornata e visualizzata.", parent=dialog)
dialog.destroy()
tk.Button(dialog, text="Salva e Visualizza", command=salva_dati_greimas).pack(pady=20)
tk.Button(dialog, text="Annulla", command=dialog.destroy).pack(pady=5)
self.app_ref.root.wait_window(dialog)
def crea_matrice_propp(self):
"""Permette all'utente di definire e visualizzare le funzioni di Propp personalizzate."""
# L'utente può definire un sottoinsieme o ridefinire le descrizioni
messagebox.showinfo("Matrice Funzioni di Propp",
"Qui puoi definire un sottoinsieme o personalizzare le descrizioni delle Funzioni di Propp.\n"
"Inserisci le funzioni nel formato 'Codice: Descrizione' (una per riga).\n"
"Se vuoi usare le 31 funzioni standard, puoi saltare questo passaggio o cliccare 'Usa Standard'.",
parent=self.app_ref.root)
dialog = tk.Toplevel(self.app_ref.root)
dialog.title("Definisci Funzioni di Propp Personalizzate")
dialog.geometry("500x600")
dialog.transient(self.app_ref.root)
dialog.grab_set()
tk.Label(dialog, text="Definisci/Modifica Funzioni di Propp:", font=("Arial", 12, "bold")).pack(pady=10)
tk.Label(dialog, text="Formato: Codice (es. F1): Descrizione", font=("Arial", 10, "italic")).pack()
# Usiamo un Text widget per permettere l'editing di più funzioni
text_area = scrolledtext.ScrolledText(dialog, wrap=tk.WORD, width=50, height=15, font=("Arial", 10))
text_area.pack(padx=10, pady=5, fill=tk.BOTH, expand=True)
# Popola con le funzioni standard o i dati utente salvati
if self.matrice_propp_data_utente:
for codice, desc in self.matrice_propp_data_utente.items():
text_area.insert(tk.END, f"{codice}: {desc}\n")
else:
# Popola con le funzioni standard come suggerimento
for codice, desc in FUNZIONI_PROPP.items():
text_area.insert(tk.END, f"{codice}: {desc}\n")
def salva_dati_propp():
input_text = text_area.get(1.0, tk.END).strip()
lines = input_text.split('\n')
new_propp_data = {}
errors = []
for line in lines:
line = line.strip()
if not line or line.startswith('#'): continue # Salta righe vuote o commenti
if ':' in line:
codice, desc = line.split(':', 1)
codice = codice.strip().upper()
desc = desc.strip()
if codice and desc:
# Opzionale: validare che il codice inizi con 'F' e sia seguito da numeri
if re.match(r'^F\d+', contesto_sx_list[k_idx+1]):
contesto_sx_str += " "
# Aggiungi spazio se è l'ultimo token SX e la parola target non è punteggiatura
elif k_idx == len(contesto_sx_list) -1 and not re.match(r'^[\.,;!?\'"]', k_tok) and not re.match(r'^[\.,;!?\'"]', k_tok) and not re.match(r'^[\.,;!?\'"]$', parola_target):
contesto_dx_str += " "
contesto_dx_str += k_tok
results_kwic.append(f"...{contesto_sx_str}[{parola_target}]{contesto_dx_str}...")
if results_kwic:
output_str = f"KWIC per '{parola_chiave}' (contesto: {contesto_size} token, {found_count} occorrenze trovate):\n\n" + "\n".join(results_kwic)
else:
output_str = f"Nessuna occorrenza trovata per '{parola_chiave}' nel corpus."
self._display_output(f"KWIC: {parola_chiave}", output_str)
def andamento(self):
"""Visualizza l'andamento della frequenza di un termine attraverso i documenti o segmenti di un singolo documento."""
if not matplotlib_disponibile:
messagebox.showerror("Libreria Mancante", "La libreria 'matplotlib' è necessaria per questa funzionalità.", parent=self.root)
return
if not self.corpus_testuale:
messagebox.showwarning("Corpus Vuoto", "Per favore, carica prima un corpus testuale.", parent=self.root)
return
parola_chiave = simpledialog.askstring("Andamento Termine", "Inserisci la parola chiave per l'analisi dell'andamento:", parent=self.root)
if not parola_chiave: return
parola_chiave_lower = parola_chiave.strip().lower()
frequencies = []
segment_labels = []
if len(self.corpus_testuale) == 1:
# Analisi per segmenti all'interno di un singolo documento
num_chunks = simpledialog.askinteger("Numero Segmenti", "Dividi il documento in quanti segmenti per l'analisi?",
parent=self.root, minvalue=2, maxvalue=100, initialvalue=10)
if num_chunks is None: return
# Ottieni tutte le parole del documento (senza rimuovere stopwords per mantenere la lunghezza originale)
words_in_doc = re.findall(r'\b\w+\b', self.corpus_testuale[0].lower())
if not words_in_doc:
self._display_output("Andamento Termine", "Il documento selezionato è vuoto o non contiene parole.")
messagebox.showwarning("Andamento Termine", "Il documento selezionato è vuoto o non contiene parole.", parent=self.root)
return
if len(words_in_doc) < num_chunks :
# Se il numero di parole è inferiore al numero di segmenti richiesti, adatta il numero di segmenti
messagebox.showwarning("Segmenti Eccessivi", f"Il documento contiene solo {len(words_in_doc)} parole. Non può essere diviso in {num_chunks} segmenti. Verrà usato un segmento per parola (max {len(words_in_doc)} segmenti).", parent=self.root)
num_chunks = len(words_in_doc)
if num_chunks < 1:
self._display_output("Andamento Termine", "Il documento non ha parole per creare segmenti.")
return
# Calcola la dimensione approssimativa di ogni segmento
chunk_size = max(1, len(words_in_doc) // num_chunks)
for i in range(num_chunks):
# Definisci gli indici di inizio e fine per il segmento corrente
start_idx = i * chunk_size
# L'ultimo segmento prende tutte le parole rimanenti
end_idx = (i + 1) * chunk_size if i < num_chunks - 1 else len(words_in_doc)
chunk = words_in_doc[start_idx:end_idx]
if chunk: # Assicurati che il segmento non sia vuoto
frequencies.append(chunk.count(parola_chiave_lower))
segment_labels.append(f"Seg. {i+1}")
if not frequencies:
self._display_output("Andamento Termine", f"La parola '{parola_chiave}' non è stata trovata o i segmenti erano vuoti.")
messagebox.showinfo("Andamento Termine", f"La parola '{parola_chiave}' non è stata trovata o i segmenti erano vuoti.", parent=self.root)
return
plot_title = f"Andamento di '{parola_chiave}' (Doc. '{self.nomi_file_corpus[0]}' in {num_chunks} segmenti)"
plot_xlabel = "Segmento del Testo"
plot_type = 'line' # Grafico a linea per l'andamento sequenziale
else:
# Analisi attraverso documenti multipli
for i, testo_doc in enumerate(self.corpus_testuale):
# Ottieni tutte le parole del documento (senza rimuovere stopwords per contare su base totale)
parole_doc = re.findall(r'\b\w+\b', testo_doc.lower())
frequencies.append(parole_doc.count(parola_chiave_lower))
segment_labels.append(self.nomi_file_corpus[i] if self.nomi_file_corpus and i < len(self.nomi_file_corpus) else f"Doc {i+1}")
plot_title = f"Andamento di '{parola_chiave}' attraverso i Documenti Caricati"
plot_xlabel = "Documento"
plot_type = 'bar' # Grafico a barre per confronto tra documenti
# Controlla se la parola chiave è stata trovata almeno una volta in tutto il corpus
if not frequencies or all(f == 0 for f in frequencies):
self._display_output("Andamento Termine", f"Nessuna occorrenza di '{parola_chiave}' trovata nel corpus per il grafico.")
messagebox.showinfo("Andamento Termine", f"La parola '{parola_chiave}' non è stata trovata nel corpus.", parent=self.root)
return
# Genera il grafico
plt.figure(figsize=(12, 7))
if plot_type == 'line':
plt.plot(segment_labels, frequencies, marker='o', linestyle='-', color='dodgerblue')
else: # plot_type == 'bar'
plt.bar(segment_labels, frequencies, color='skyblue')
plt.title(plot_title, fontsize=14)
plt.xlabel(plot_xlabel, fontsize=12)
plt.ylabel("Frequenza Assoluta", fontsize=12)
# Ruota le etichette sull'asse x se sono molte per evitare sovrapposizioni
if len(segment_labels) > 10:
plt.xticks(rotation=45, ha="right", fontsize=10)
else:
plt.xticks(fontsize=10)
plt.yticks(fontsize=10)
plt.grid(axis='y', linestyle='--')
plt.tight_layout() # Adatta il layout per evitare tagli
plt.show()
self._display_output("Andamento Termine", f"Grafico dell'andamento di '{parola_chiave}' generato e visualizzato.")
def vista_rete(self):
"""Calcola e visualizza le co-occorrenze più frequenti tra termini (stopwords escluse) in una finestra di contesto."""
if not self.corpus_testuale:
messagebox.showwarning("Corpus Vuoto", "Per favore, carica prima un corpus testuale.", parent=self.root)
return
window_size = simpledialog.askinteger("Finestra di Contesto (Co-occorrenze)",
"Inserisci la dimensione della finestra di contesto (numero di parole vicine da considerare):",
parent=self.root, minvalue=2, maxvalue=10, initialvalue=3)
if window_size is None: return
num_cooc = simpledialog.askinteger("Numero Co-occorrenze",
"Quante coppie di co-occorrenze più frequenti vuoi visualizzare?",
parent=self.root, minvalue=1, initialvalue=15)
if num_cooc is None: return
# Ottieni le parole processate (minuscolo, senza stopwords)
parole = self._get_processed_words(remove_stopwords=True)
if len(parole) < window_size:
self._display_output("Rete Co-occorrenze", f"Non ci sono abbastanza parole nel corpus (dopo rimozione stopwords) per analizzare le co-occorrenze con una finestra di dimensione {window_size}.")
messagebox.showinfo("Rete Co-occorrenze", "Testo insufficiente per l'analisi delle co-occorrenze.", parent=self.root)
return
co_occurrences = Counter()
# Itera attraverso le parole per creare finestre di contesto
for i in range(len(parole) - window_size + 1):
window_segment = parole[i : i + window_size]
# Considera solo le parole uniche all'interno della finestra e ordinale per creare coppie consistenti
parole_nella_finestra_uniche = sorted(list(set(window_segment)))
# Se ci sono almeno due parole uniche nella finestra, genera tutte le coppie possibili
if len(parole_nella_finestra_uniche) < 2: continue
# Genera tutte le coppie non ordinate di parole uniche nella finestra
for j in range(len(parole_nella_finestra_uniche)):
for k in range(j + 1, len(parole_nella_finestra_uniche)):
pair = tuple(sorted((parole_nella_finestra_uniche[j], parole_nella_finestra_uniche[k])))
co_occurrences[pair] += 1
if not co_occurrences:
self._display_output("Rete Co-occorrenze", "Nessuna co-occorrenza trovata con i parametri specificati.")
messagebox.showinfo("Rete Co-occorrenze", "Nessuna co-occorrenza trovata.", parent=self.root)
return
output_str = f"Le {num_cooc} coppie di termini co-occorrenti più frequenti (finestra: {window_size} parole, stopwords escluse):\n"
output_str += "--------------------------------------------------------------------------------------\n"
for (p1, p2), freq in co_occurrences.most_common(num_cooc):
output_str += f"('{p1}', '{p2}'): {freq}\n"
output_str += "\nNota: Questa è una rappresentazione testuale. Per una visualizzazione grafica della rete, sarebbero necessarie librerie aggiuntive (es. NetworkX, Matplotlib)."
self._display_output("Rete di Co-occorrenze", output_str)
messagebox.showinfo("Rete di Co-occorrenze", "Analisi delle co-occorrenze completata. I risultati sono nell'area di output.", parent=self.root)
# --- Blocco Principale per l'Esecuzione dell'Applicazione ---
if __name__ == '__main__':
# Inizializza la finestra principale di Tkinter
radice = tk.Tk()
# Crea un'istanza della classe principale dell'applicazione
app = StrumentiTestualiUsai(radice)
# Avvia il loop principale dell'interfaccia grafica
radice.mainloop()
We use cookies on our website to give you the most relevant experience by remembering your preferences and repeat visits. By clicking “Accept All”, you consent to the use of ALL the cookies. However, you may visit "Cookie Settings" to provide a controlled consent.
This website uses cookies to improve your experience while you navigate through the website. Out of these, the cookies that are categorized as necessary are stored on your browser as they are essential for the working of basic functionalities of the website. We also use third-party cookies that help us analyze and understand how you use this website. These cookies will be stored in your browser only with your consent. You also have the option to opt-out of these cookies. But opting out of some of these cookies may affect your browsing experience.
Necessary cookies are absolutely essential for the website to function properly. These cookies ensure basic functionalities and security features of the website, anonymously.
Cookie
Durata
Descrizione
cookielawinfo-checkbox-analytics
11 months
This cookie is set by GDPR Cookie Consent plugin. The cookie is used to store the user consent for the cookies in the category "Analytics".
cookielawinfo-checkbox-functional
11 months
The cookie is set by GDPR cookie consent to record the user consent for the cookies in the category "Functional".
cookielawinfo-checkbox-necessary
11 months
This cookie is set by GDPR Cookie Consent plugin. The cookies is used to store the user consent for the cookies in the category "Necessary".
cookielawinfo-checkbox-others
11 months
This cookie is set by GDPR Cookie Consent plugin. The cookie is used to store the user consent for the cookies in the category "Other.
cookielawinfo-checkbox-performance
11 months
This cookie is set by GDPR Cookie Consent plugin. The cookie is used to store the user consent for the cookies in the category "Performance".
viewed_cookie_policy
11 months
The cookie is set by the GDPR Cookie Consent plugin and is used to store whether or not user has consented to the use of cookies. It does not store any personal data.
Functional cookies help to perform certain functionalities like sharing the content of the website on social media platforms, collect feedbacks, and other third-party features.
Performance cookies are used to understand and analyze the key performance indexes of the website which helps in delivering a better user experience for the visitors.
Analytical cookies are used to understand how visitors interact with the website. These cookies help provide information on metrics the number of visitors, bounce rate, traffic source, etc.
Advertisement cookies are used to provide visitors with relevant ads and marketing campaigns. These cookies track visitors across websites and collect information to provide customized ads.